PageRenderTime 78ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/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
  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, left $xx
  823. // Reverb bounces, right $xx
  824. // Reverb feedback, left to left $xx
  825. // Reverb feedback, left to right $xx
  826. // Reverb feedback, right to right $xx
  827. // Reverb feedback, right to left $xx
  828. // Premix left to right $xx
  829. // Premix right to left $xx
  830. if (! $this->IsWithinBitRange($source_data_array['left'], 16, false))
  831. {
  832. $this->errors[] = 'Invalid Reverb Left in ' . $frame_name . ' (' . $source_data_array['left'] . ') (range = 0 to 65535)';
  833. }
  834. elseif (! $this->IsWithinBitRange($source_data_array['right'], 16, false))
  835. {
  836. $this->errors[] = 'Invalid Reverb Left in ' . $frame_name . ' (' . $source_data_array['right'] . ') (range = 0 to 65535)';
  837. }
  838. elseif (! $this->IsWithinBitRange($source_data_array['bouncesL'], 8, false))
  839. {
  840. $this->errors[] = 'Invalid Reverb Bounces, Left in ' . $frame_name . ' (' . $source_data_array['bouncesL'] . ') (range = 0 to 255)';
  841. }
  842. elseif (! $this->IsWithinBitRange($source_data_array['bouncesR'], 8, false))
  843. {
  844. $this->errors[] = 'Invalid Reverb Bounces, Right in ' . $frame_name . ' (' . $source_data_array['bouncesR'] . ') (range = 0 to 255)';
  845. }
  846. elseif (! $this->IsWithinBitRange($source_data_array['feedbackLL'], 8, false))
  847. {
  848. $this->errors[] = 'Invalid Reverb Feedback, Left-To-Left in ' . $frame_name . ' (' . $source_data_array['feedbackLL'] . ') (range = 0 to 255)';
  849. }
  850. elseif (! $this->IsWithinBitRange($source_data_array['feedbackLR'], 8, false))
  851. {
  852. $this->errors[] = 'Invalid Reverb Feedback, Left-To-Right in ' . $frame_name . ' (' . $source_data_array['feedbackLR'] . ') (range = 0 to 255)';
  853. }
  854. elseif (! $this->IsWithinBitRange($source_data_array['feedbackRR'], 8, false))
  855. {
  856. $this->errors[] = 'Invalid Reverb Feedback, Right-To-Right in ' . $frame_name . ' (' . $source_data_array['feedbackRR'] . ') (range = 0 to 255)';
  857. }
  858. elseif (! $this->IsWithinBitRange($source_data_array['feedbackRL'], 8, false))
  859. {
  860. $this->errors[] = 'Invalid Reverb Feedback, Right-To-Left in ' . $frame_name . ' (' . $source_data_array['feedbackRL'] . ') (range = 0 to 255)';
  861. }
  862. elseif (! $this->IsWithinBitRange($source_data_array['premixLR'], 8, false))
  863. {
  864. $this->errors[] = 'Invalid Premix, Left-To-Right in ' . $frame_name . ' (' . $source_data_array['premixLR'] . ') (range = 0 to 255)';
  865. }
  866. elseif (! $this->IsWithinBitRange($source_data_array['premixRL'], 8, false))
  867. {
  868. $this->errors[] = 'Invalid Premix, Right-To-Left in ' . $frame_name . ' (' . $source_data_array['premixRL'] . ') (range = 0 to 255)';
  869. }
  870. else
  871. {
  872. $framedata .= getid3_lib :: BigEndian2String($source_data_array['left'], 2, false);
  873. $framedata .= getid3_lib :: BigEndian2String($source_data_array['right'], 2, false);
  874. $framedata .= chr($source_data_array['bouncesL']);
  875. $framedata .= chr($source_data_array['bouncesR']);
  876. $framedata .= chr($source_data_array['feedbackLL']);
  877. $framedata .= chr($source_data_array['feedbackLR']);
  878. $framedata .= chr($source_data_array['feedbackRR']);
  879. $framedata .= chr($source_data_array['feedbackRL']);
  880. $framedata .= chr($source_data_array['premixLR']);
  881. $framedata .= chr($source_data_array['premixRL']);
  882. }
  883. break;
  884. case 'APIC' :
  885. // 4.14 APIC Attached picture
  886. // Text encoding $xx
  887. // MIME type <text string> $00
  888. // Picture type $xx
  889. // Description <text string according to encoding> $00 (00)
  890. // Picture data <binary data>
  891. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  892. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  893. {
  894. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  895. }
  896. elseif (! $this->ID3v2IsValidAPICpicturetype($source_data_array['picturetypeid']))
  897. {
  898. $this->errors[] = 'Invalid Picture Type byte in ' . $frame_name . ' (' . $source_data_array['picturetypeid'] . ') for ID3v2.' . $this->majorversion;
  899. }
  900. elseif (($this->majorversion >= 3) && (! $this->ID3v2IsValidAPICimageformat($source_data_array['mime'])))
  901. {
  902. $this->errors[] = 'Invalid MIME Type in ' . $frame_name . ' (' . $source_data_array['mime'] . ') for ID3v2.' . $this->majorversion;
  903. }
  904. elseif (($source_data_array['mime'] == '-->') && (! $this->IsValidURL($source_data_array['data'], false, false)))
  905. {
  906. //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
  907. // probably should be an error, need to rewrite IsValidURL() to handle other encodings
  908. $this->warnings[] = 'Invalid URL in ' . $frame_name . ' (' . $source_data_array['data'] . ')';
  909. }
  910. else
  911. {
  912. $framedata .= chr($source_data_array['encodingid']);
  913. $framedata .= str_replace("\x00", '', $source_data_array['mime']) . "\x00";
  914. $framedata .= chr($source_data_array['picturetypeid']);
  915. $framedata .= @$source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  916. $framedata .= $source_data_array['data'];
  917. }
  918. break;
  919. case 'GEOB' :
  920. // 4.15 GEOB General encapsulated object
  921. // Text encoding $xx
  922. // MIME type <text string> $00
  923. // Filename <text string according to encoding> $00 (00)
  924. // Content description <text string according to encoding> $00 (00)
  925. // Encapsulated object <binary data>
  926. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  927. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  928. {
  929. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  930. }
  931. elseif (! $this->IsValidMIMEstring($source_data_array['mime']))
  932. {
  933. $this->errors[] = 'Invalid MIME Type in ' . $frame_name . ' (' . $source_data_array['mime'] . ')';
  934. }
  935. elseif (! $source_data_array['description'])
  936. {
  937. $this->errors[] = 'Missing Description in ' . $frame_name;
  938. }
  939. else
  940. {
  941. $framedata .= chr($source_data_array['encodingid']);
  942. $framedata .= str_replace("\x00", '', $source_data_array['mime']) . "\x00";
  943. $framedata .= $source_data_array['filename'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  944. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  945. $framedata .= $source_data_array['data'];
  946. }
  947. break;
  948. case 'PCNT' :
  949. // 4.16 PCNT Play counter
  950. // When the counter reaches all one's, one byte is inserted in
  951. // front of the counter thus making the counter eight bits bigger
  952. // Counter $xx xx xx xx (xx ...)
  953. $framedata .= getid3_lib :: BigEndian2String($source_data_array['data'], 4, false);
  954. break;
  955. case 'POPM' :
  956. // 4.17 POPM Popularimeter
  957. // When the counter reaches all one's, one byte is inserted in
  958. // front of the counter thus making the counter eight bits bigger
  959. // Email to user <text string> $00
  960. // Rating $xx
  961. // Counter $xx xx xx xx (xx ...)
  962. if (! $this->IsWithinBitRange($source_data_array['rating'], 8, false))
  963. {
  964. $this->errors[] = 'Invalid Rating byte in ' . $frame_name . ' (' . $source_data_array['rating'] . ') (range = 0 to 255)';
  965. }
  966. elseif (! IsValidEmail($source_data_array['email']))
  967. {
  968. $this->errors[] = 'Invalid Email in ' . $frame_name . ' (' . $source_data_array['email'] . ')';
  969. }
  970. else
  971. {
  972. $framedata .= str_replace("\x00", '', $source_data_array['email']) . "\x00";
  973. $framedata .= chr($source_data_array['rating']);
  974. $framedata .= getid3_lib :: BigEndian2String($source_data_array['data'], 4, false);
  975. }
  976. break;
  977. case 'RBUF' :
  978. // 4.18 RBUF Recommended buffer size
  979. // Buffer size $xx xx xx
  980. // Embedded info flag %0000000x
  981. // Offset to next tag $xx xx xx xx
  982. if (! $this->IsWithinBitRange($source_data_array['buffersize'], 24, false))
  983. {
  984. $this->errors[] = 'Invalid Buffer Size in ' . $frame_name;
  985. }
  986. elseif (! $this->IsWithinBitRange($source_data_array['nexttagoffset'], 32, false))
  987. {
  988. $this->errors[] = 'Invalid Offset To Next Tag in ' . $frame_name;
  989. }
  990. else
  991. {
  992. $framedata .= getid3_lib :: BigEndian2String($source_data_array['buffersize'], 3, false);
  993. $flag .= '0000000';
  994. $flag .= $source_data_array['flags']['embededinfo'] ? '1' : '0';
  995. $framedata .= chr(bindec($flag));
  996. $framedata .= getid3_lib :: BigEndian2String($source_data_array['nexttagoffset'], 4, false);
  997. }
  998. break;
  999. case 'AENC' :
  1000. // 4.19 AENC Audio encryption
  1001. // Owner identifier <text string> $00
  1002. // Preview start $xx xx
  1003. // Preview length $xx xx
  1004. // Encryption info <binary data>
  1005. if (! $this->IsWithinBitRange($source_data_array['previewstart'], 16, false))
  1006. {
  1007. $this->errors[] = 'Invalid Preview Start in ' . $frame_name . ' (' . $source_data_array['previewstart'] . ')';
  1008. }
  1009. elseif (! $this->IsWithinBitRange($source_data_array['previewlength'], 16, false))
  1010. {
  1011. $this->errors[] = 'Invalid Preview Length in ' . $frame_name . ' (' . $source_data_array['previewlength'] . ')';
  1012. }
  1013. else
  1014. {
  1015. $framedata .= str_replace("\x00", '', $source_data_array['ownerid']) . "\x00";
  1016. $framedata .= getid3_lib :: BigEndian2String($source_data_array['previewstart'], 2, false);
  1017. $framedata .= getid3_lib :: BigEndian2String($source_data_array['previewlength'], 2, false);
  1018. $framedata .= $source_data_array['encryptioninfo'];
  1019. }
  1020. break;
  1021. case 'LINK' :
  1022. // 4.20 LINK Linked information
  1023. // Frame identifier $xx xx xx xx
  1024. // URL <text string> $00
  1025. // ID and additional data <text string(s)>
  1026. if (! getid3_id3v2 :: IsValidID3v2FrameName($source_data_array['frameid'], $this->majorversion))
  1027. {
  1028. $this->errors[] = 'Invalid Frame Identifier in ' . $frame_name . ' (' . $source_data_array['frameid'] . ')';
  1029. }
  1030. elseif (! $this->IsValidURL($source_data_array['data'], true, false))
  1031. {
  1032. //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
  1033. // probably should be an error, need to rewrite IsValidURL() to handle other encodings
  1034. $this->warnings[] = 'Invalid URL in ' . $frame_name . ' (' . $source_data_array['data'] . ')';
  1035. }
  1036. elseif ((($source_data_array['frameid'] == 'AENC') || ($source_data_array['frameid'] == 'APIC') || ($source_data_array['frameid'] == 'GEOB') || ($source_data_array['frameid'] == 'TXXX')) && ($source_data_array['additionaldata'] == ''))
  1037. {
  1038. $this->errors[] = 'Content Descriptor must be specified as additional data for Frame Identifier of ' . $source_data_array['frameid'] . ' in ' . $frame_name;
  1039. }
  1040. elseif (($source_data_array['frameid'] == 'USER') && (getid3_id3v2 :: LanguageLookup($source_data_array['additionaldata'], true) == ''))
  1041. {
  1042. $this->errors[] = 'Language must be specified as additional data for Frame Identifier of ' . $source_data_array['frameid'] . ' in ' . $frame_name;
  1043. }
  1044. elseif (($source_data_array['frameid'] == 'PRIV') && ($source_data_array['additionaldata'] == ''))
  1045. {
  1046. $this->errors[] = 'Owner Identifier must be specified as additional data for Frame Identifier of ' . $source_data_array['frameid'] . ' in ' . $frame_name;
  1047. }
  1048. elseif ((($source_data_array['frameid'] == 'COMM') || ($source_data_array['frameid'] == 'SYLT') || ($source_data_array['frameid'] == 'USLT')) && ((getid3_id3v2 :: LanguageLookup(substr($source_data_array['additionaldata'], 0, 3), true) == '') || (substr($source_data_array['additionaldata'], 3) == '')))
  1049. {
  1050. $this->errors[] = 'Language followed by Content Descriptor must be specified as additional data for Frame Identifier of ' . $source_data_array['frameid'] . ' in ' . $frame_name;
  1051. }
  1052. else
  1053. {
  1054. $framedata .= $source_data_array['frameid'];
  1055. $framedata .= str_replace("\x00", '', $source_data_array['data']) . "\x00";
  1056. switch ($source_data_array['frameid'])
  1057. {
  1058. case 'COMM' :
  1059. case 'SYLT' :
  1060. case 'USLT' :
  1061. case 'PRIV' :
  1062. case 'USER' :
  1063. case 'AENC' :
  1064. case 'APIC' :
  1065. case 'GEOB' :
  1066. case 'TXXX' :
  1067. $framedata .= $source_data_array['additionaldata'];
  1068. break;
  1069. case 'ASPI' :
  1070. case 'ETCO' :
  1071. case 'EQU2' :
  1072. case 'MCID' :
  1073. case 'MLLT' :
  1074. case 'OWNE' :
  1075. case 'RVA2' :
  1076. case 'RVRB' :
  1077. case 'SYTC' :
  1078. case 'IPLS' :
  1079. case 'RVAD' :
  1080. case 'EQUA' :
  1081. // no additional data required
  1082. break;
  1083. case 'RBUF' :
  1084. if ($this->majorversion == 3)
  1085. {
  1086. // no additional data required
  1087. }
  1088. else
  1089. {
  1090. $this->errors[] = $source_data_array['frameid'] . ' is not a valid Frame Identifier in ' . $frame_name . ' (in ID3v2.' . $this->majorversion . ')';
  1091. }
  1092. default :
  1093. if ((substr($source_data_array['frameid'], 0, 1) == 'T') || (substr($source_data_array['frameid'], 0, 1) == 'W'))
  1094. {
  1095. // no additional data required
  1096. }
  1097. else
  1098. {
  1099. $this->errors[] = $source_data_array['frameid'] . ' is not a valid Frame Identifier in ' . $frame_name . ' (in ID3v2.' . $this->majorversion . ')';
  1100. }
  1101. break;
  1102. }
  1103. }
  1104. break;
  1105. case 'POSS' :
  1106. // 4.21 POSS Position synchronisation frame (ID3v2.3+ only)
  1107. // Time stamp format $xx
  1108. // Position $xx (xx ...)
  1109. if (($source_data_array['timestampformat'] < 1) || ($source_data_array['timestampformat'] > 2))
  1110. {
  1111. $this->errors[] = 'Invalid Time Stamp Format in ' . $frame_name . ' (' . $source_data_array['timestampformat'] . ') (valid = 1 or 2)';
  1112. }
  1113. elseif (! $this->IsWithinBitRange($source_data_array['position'], 32, false))
  1114. {
  1115. $this->errors[] = 'Invalid Position in ' . $frame_name . ' (' . $source_data_array['position'] . ') (range = 0 to 4294967295)';
  1116. }
  1117. else
  1118. {
  1119. $framedata .= chr($source_data_array['timestampformat']);
  1120. $framedata .= getid3_lib :: BigEndian2String($source_data_array['position'], 4, false);
  1121. }
  1122. break;
  1123. case 'USER' :
  1124. // 4.22 USER Terms of use (ID3v2.3+ only)
  1125. // Text encoding $xx
  1126. // Language $xx xx xx
  1127. // The actual text <text string according to encoding>
  1128. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  1129. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  1130. {
  1131. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ')';
  1132. }
  1133. elseif (getid3_id3v2 :: LanguageLookup($source_data_array['language'], true) == '')
  1134. {
  1135. $this->errors[] = 'Invalid Language in ' . $frame_name . ' (' . $source_data_array['language'] . ')';
  1136. }
  1137. else
  1138. {
  1139. $framedata .= chr($source_data_array['encodingid']);
  1140. $framedata .= strtolower($source_data_array['language']);
  1141. $framedata .= $source_data_array['data'];
  1142. }
  1143. break;
  1144. case 'OWNE' :
  1145. // 4.23 OWNE Ownership frame (ID3v2.3+ only)
  1146. // Text encoding $xx
  1147. // Price paid <text string> $00
  1148. // Date of purch. <text string>
  1149. // Seller <text string according to encoding>
  1150. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  1151. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  1152. {
  1153. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ')';
  1154. }
  1155. elseif (! $this->IsANumber($source_data_array['pricepaid']['value'], false))
  1156. {
  1157. $this->errors[] = 'Invalid Price Paid in ' . $frame_name . ' (' . $source_data_array['pricepaid']['value'] . ')';
  1158. }
  1159. elseif (! $this->IsValidDateStampString($source_data_array['purchasedate']))
  1160. {
  1161. $this->errors[] = 'Invalid Date Of Purchase in ' . $frame_name . ' (' . $source_data_array['purchasedate'] . ') (format = YYYYMMDD)';
  1162. }
  1163. else
  1164. {
  1165. $framedata .= chr($source_data_array['encodingid']);
  1166. $framedata .= str_replace("\x00", '', $source_data_array['pricepaid']['value']) . "\x00";
  1167. $framedata .= $source_data_array['purchasedate'];
  1168. $framedata .= $source_data_array['seller'];
  1169. }
  1170. break;
  1171. case 'COMR' :
  1172. // 4.24 COMR Commercial frame (ID3v2.3+ only)
  1173. // Text encoding $xx
  1174. // Price string <text string> $00
  1175. // Valid until <text string>
  1176. // Contact URL <text string> $00
  1177. // Received as $xx
  1178. // Name of seller <text string according to encoding> $00 (00)
  1179. // Description <text string according to encoding> $00 (00)
  1180. // Picture MIME type <string> $00
  1181. // Seller logo <binary data>
  1182. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  1183. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  1184. {
  1185. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ')';
  1186. }
  1187. elseif (! $this->IsValidDateStampString($source_data_array['pricevaliduntil']))
  1188. {
  1189. $this->errors[] = 'Invalid Valid Until date in ' . $frame_name . ' (' . $source_data_array['pricevaliduntil'] . ') (format = YYYYMMDD)';
  1190. }
  1191. elseif (! $this->IsValidURL($source_data_array['contacturl'], false, true))
  1192. {
  1193. $this->errors[] = 'Invalid Contact URL in ' . $frame_name . ' (' . $source_data_array['contacturl'] . ') (allowed schemes: http, https, ftp, mailto)';
  1194. }
  1195. elseif (! $this->ID3v2IsValidCOMRreceivedAs($source_data_array['receivedasid']))
  1196. {
  1197. $this->errors[] = 'Invalid Received As byte in ' . $frame_name . ' (' . $source_data_array['contacturl'] . ') (range = 0 to 8)';
  1198. }
  1199. elseif (! $this->IsValidMIMEstring($source_data_array['mime']))
  1200. {
  1201. $this->errors[] = 'Invalid MIME Type in ' . $frame_name . ' (' . $source_data_array['mime'] . ')';
  1202. }
  1203. else
  1204. {
  1205. $framedata .= chr($source_data_array['encodingid']);
  1206. unset($pricestring);
  1207. foreach ($source_data_array['price'] as $key => $val)
  1208. {
  1209. if ($this->ID3v2IsValidPriceString($key . $val['value']))
  1210. {
  1211. $pricestrings[] = $key . $val['value'];
  1212. }
  1213. else
  1214. {
  1215. $this->errors[] = 'Invalid Price String in ' . $frame_name . ' (' . $key . $val['value'] . ')';
  1216. }
  1217. }
  1218. $framedata .= implode('/', $pricestrings);
  1219. $framedata .= $source_data_array['pricevaliduntil'];
  1220. $framedata .= str_replace("\x00", '', $source_data_array['contacturl']) . "\x00";
  1221. $framedata .= chr($source_data_array['receivedasid']);
  1222. $framedata .= $source_data_array['sellername'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  1223. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  1224. $framedata .= $source_data_array['mime'] . "\x00";
  1225. $framedata .= $source_data_array['logo'];
  1226. }
  1227. break;
  1228. case 'ENCR' :
  1229. // 4.25 ENCR Encryption method registration (ID3v2.3+ only)
  1230. // Owner identifier <text string> $00
  1231. // Method symbol $xx
  1232. // Encryption data <binary data>
  1233. if (! $this->IsWithinBitRange($source_data_array['methodsymbol'], 8, false))
  1234. {
  1235. $this->errors[] = 'Invalid Group Symbol in ' . $frame_name . ' (' . $source_data_array['methodsymbol'] . ') (range = 0 to 255)';
  1236. }
  1237. else
  1238. {
  1239. $framedata .= str_replace("\x00", '', $source_data_array['ownerid']) . "\x00";
  1240. $framedata .= ord($source_data_array['methodsymbol']);
  1241. $framedata .= $source_data_array['data'];
  1242. }
  1243. break;
  1244. case 'GRID' :
  1245. // 4.26 GRID Group identification registration (ID3v2.3+ only)
  1246. // Owner identifier <text string> $00
  1247. // Group symbol $xx
  1248. // Group dependent data <binary data>
  1249. if (! $this->IsWithinBitRange($source_data_array['groupsymbol'], 8, false))
  1250. {
  1251. $this->errors[] = 'Invalid Group Symbol in ' . $frame_name . ' (' . $source_data_array['groupsymbol'] . ') (range = 0 to 255)';
  1252. }
  1253. else
  1254. {
  1255. $framedata .= str_replace("\x00", '', $source_data_array['ownerid']) . "\x00";
  1256. $framedata .= ord($source_data_array['groupsymbol']);
  1257. $framedata .= $source_data_array['data'];
  1258. }
  1259. break;
  1260. case 'PRIV' :
  1261. // 4.27 PRIV Private frame (ID3v2.3+ only)
  1262. // Owner identifier <text string> $00
  1263. // The private data <binary data>
  1264. $framedata .= str_replace("\x00", '', $source_data_array['ownerid']) . "\x00";
  1265. $framedata .= $source_data_array['data'];
  1266. break;
  1267. case 'SIGN' :
  1268. // 4.28 SIGN Signature frame (ID3v2.4+ only)
  1269. // Group symbol $xx
  1270. // Signature <binary data>
  1271. if (! $this->IsWithinBitRange($source_data_array['groupsymbol'], 8, false))
  1272. {
  1273. $this->errors[] = 'Invalid Group Symbol in ' . $frame_name . ' (' . $source_data_array['groupsymbol'] . ') (range = 0 to 255)';
  1274. }
  1275. else
  1276. {
  1277. $framedata .= ord($source_data_array['groupsymbol']);
  1278. $framedata .= $source_data_array['data'];
  1279. }
  1280. break;
  1281. case 'SEEK' :
  1282. // 4.29 SEEK Seek frame (ID3v2.4+ only)
  1283. // Minimum offset to next tag $xx xx xx xx
  1284. if (! $this->IsWithinBitRange($source_data_array['data'], 32, false))
  1285. {
  1286. $this->errors[] = 'Invalid Minimum Offset in ' . $frame_name . ' (' . $source_data_array['data'] . ') (range = 0 to 4294967295)';
  1287. }
  1288. else
  1289. {
  1290. $framedata .= getid3_lib :: BigEndian2String($source_data_array['data'], 4, false);
  1291. }
  1292. break;
  1293. case 'ASPI' :
  1294. // 4.30 ASPI Audio seek point index (ID3v2.4+ only)
  1295. // Indexed data start (S) $xx xx xx xx
  1296. // Indexed data length (L) $xx xx xx xx
  1297. // Number of index points (N) $xx xx
  1298. // Bits per index point (b) $xx
  1299. // Then for every index point the following data is included:
  1300. // Fraction at index (Fi) $xx (xx)
  1301. if (! $this->IsWithinBitRange($source_data_array['datastart'], 32, false))
  1302. {
  1303. $this->errors[] = 'Invalid Indexed Data Start in ' . $frame_name . ' (' . $source_data_array['datastart'] . ') (range = 0 to 4294967295)';
  1304. }
  1305. elseif (! $this->IsWithinBitRange($source_data_array['datalength'], 32, false))
  1306. {
  1307. $this->errors[] = 'Invalid Indexed Data Length in ' . $frame_name . ' (' . $source_data_array['datalength'] . ') (range = 0 to 4294967295)';
  1308. }
  1309. elseif (! $this->IsWithinBitRange($source_data_array['indexpoints'], 16, false))
  1310. {
  1311. $this->errors[] = 'Invalid Number Of Index Points in ' . $frame_name . ' (' . $source_data_array['indexpoints'] . ') (range = 0 to 65535)';
  1312. }
  1313. elseif (! $this->IsWithinBitRange($source_data_array['bitsperpoint'], 8, false))
  1314. {
  1315. $this->errors[] = 'Invalid Bits Per Index Point in ' . $frame_name . ' (' . $source_data_array['bitsperpoint'] . ') (range = 0 to 255)';
  1316. }
  1317. elseif ($source_data_array['indexpoints'] != count($source_data_array['indexes']))
  1318. {
  1319. $this->errors[] = 'Number Of Index Points does not match actual supplied data in ' . $frame_name;
  1320. }
  1321. else
  1322. {
  1323. $framedata .= getid3_lib :: BigEndian2String($source_data_array['datastart'], 4, false);
  1324. $framedata .= getid3_lib :: BigEndian2String($source_data_array['datalength'], 4, false);
  1325. $framedata .= getid3_lib :: BigEndian2String($source_data_array['indexpoints'], 2, false);
  1326. $framedata .= getid3_lib :: BigEndian2String($source_data_array['bitsperpoint'], 1, false);
  1327. foreach ($source_data_array['indexes'] as $key => $val)
  1328. {
  1329. $framedata .= getid3_lib :: BigEndian2String($val, ceil($source_data_array['bitsperpoint'] / 8), false);
  1330. }
  1331. }
  1332. break;
  1333. case 'RGAD' :
  1334. // RGAD Replay Gain Adjustment
  1335. // http://privatewww.essex.ac.uk/~djmrob/replaygain/
  1336. // Peak Amplitude $xx $xx $xx $xx
  1337. // Radio Replay Gain Adjustment %aaabbbcd %dddddddd
  1338. // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd
  1339. // a - name code
  1340. // b - originator code
  1341. // c - sign bit
  1342. // d - replay gain adjustment
  1343. if (($source_data_array['track_adjustment'] > 51) || ($source_data_array['track_adjustment'] < - 51))
  1344. {
  1345. $this->errors[] = 'Invalid Track Adjustment in ' . $frame_name . ' (' . $source_data_array['track_adjustment'] . ') (range = -51.0 to +51.0)';
  1346. }
  1347. elseif (($source_data_array['album_adjustment'] > 51) || ($source_data_array['album_adjustment'] < - 51))
  1348. {
  1349. $this->errors[] = 'Invalid Album Adjustment in ' . $frame_name . ' (' . $source_data_array['album_adjustment'] . ') (range = -51.0 to +51.0)';
  1350. }
  1351. elseif (! $this->ID3v2IsValidRGADname($source_data_array['raw']['track_name']))
  1352. {
  1353. $this->errors[] = 'Invalid Track Name Code in ' . $frame_name . ' (' . $source_data_array['raw']['track_name'] . ') (range = 0 to 2)';
  1354. }
  1355. elseif (! $this->ID3v2IsValidRGADname($source_data_array['raw']['album_name']))
  1356. {
  1357. $this->errors[] = 'Invalid Album Name Code in ' . $frame_name . ' (' . $source_data_array['raw']['album_name'] . ') (range = 0 to 2)';
  1358. }
  1359. elseif (! $this->ID3v2IsValidRGADoriginator($source_data_array['raw']['track_originator']))
  1360. {
  1361. $this->errors[] = 'Invalid Track Originator Code in ' . $frame_name . ' (' . $source_data_array['raw']['track_originator'] . ') (range = 0 to 3)';
  1362. }
  1363. elseif (! $this->ID3v2IsValidRGADoriginator($source_data_array['raw']['album_originator']))
  1364. {
  1365. $this->errors[] = 'Invalid Album Originator Code in ' . $frame_name . ' (' . $source_data_array['raw']['album_originator'] . ') (range = 0 to 3)';
  1366. }
  1367. else
  1368. {
  1369. $framedata .= getid3_lib :: Float2String($source_data_array['peakamplitude'], 32);
  1370. $framedata .= getid3_lib :: RGADgainString($source_data_array['raw']['track_name'], $source_data_array['raw']['track_originator'], $source_data_array['track_adjustment']);
  1371. $framedata .= getid3_lib :: RGADgainString($source_data_array['raw']['album_name'], $source_data_array['raw']['album_originator'], $source_data_array['album_adjustment']);
  1372. }
  1373. break;
  1374. default :
  1375. if ($frame_name{0} == 'T')
  1376. {
  1377. // 4.2. T??? Text information frames
  1378. // Text encoding $xx
  1379. // Information <text string(s) according to encoding>
  1380. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  1381. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  1382. {
  1383. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  1384. }
  1385. else
  1386. {
  1387. $framedata .= chr($source_data_array['encodingid']);
  1388. $framedata .= $source_data_array['data'];
  1389. }
  1390. }
  1391. elseif ($frame_name{0} == 'W')
  1392. {
  1393. // 4.3. W??? URL link frames
  1394. // URL <text string>
  1395. if (! $this->IsValidURL($source_data_array['data'], false, false))
  1396. {
  1397. //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
  1398. // probably should be an error, need to rewrite IsValidURL() to handle other encodings
  1399. $this->warnings[] = 'Invalid URL in ' . $frame_name . ' (' . $source_data_array['data'] . ')';
  1400. }
  1401. else
  1402. {
  1403. $framedata .= $source_data_array['data'];
  1404. }
  1405. }
  1406. else
  1407. {
  1408. $this->errors[] = $frame_name . ' not yet supported in $this->GenerateID3v2FrameData()';
  1409. }
  1410. break;
  1411. }
  1412. }
  1413. if (! empty($this->errors))
  1414. {
  1415. return false;
  1416. }
  1417. return $framedata;
  1418. }
  1419. function ID3v2FrameIsAllowed($frame_name, $source_data_array)
  1420. {
  1421. static $PreviousFrames = array();
  1422. if ($frame_name === null)
  1423. {
  1424. // if the writing functions are called multiple times, the static array needs to be
  1425. // cleared - this can be done by calling $this->ID3v2FrameIsAllowed(null, '')
  1426. $PreviousFrames = array();
  1427. return true;
  1428. }
  1429. if ($this->majorversion == 4)
  1430. {
  1431. switch ($frame_name)
  1432. {
  1433. case 'UFID' :
  1434. case 'AENC' :
  1435. case 'ENCR' :
  1436. case 'GRID' :
  1437. if (! isset($source_data_array['ownerid']))
  1438. {
  1439. $this->errors[] = '[ownerid] not specified for ' . $frame_name;
  1440. }
  1441. elseif (in_array($frame_name . $source_data_array['ownerid'], $PreviousFrames))
  1442. {
  1443. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same OwnerID (' . $source_data_array['ownerid'] . ')';
  1444. }
  1445. else
  1446. {
  1447. $PreviousFrames[] = $frame_name . $source_data_array['ownerid'];
  1448. }
  1449. break;
  1450. case 'TXXX' :
  1451. case 'WXXX' :
  1452. case 'RVA2' :
  1453. case 'EQU2' :
  1454. case 'APIC' :
  1455. case 'GEOB' :
  1456. if (! isset($source_data_array['description']))
  1457. {
  1458. $this->errors[] = '[description] not specified for ' . $frame_name;
  1459. }
  1460. elseif (in_array($frame_name . $source_data_array['description'], $PreviousFrames))
  1461. {
  1462. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Description (' . $source_data_array['description'] . ')';
  1463. }
  1464. else
  1465. {
  1466. $PreviousFrames[] = $frame_name . $source_data_array['description'];
  1467. }
  1468. break;
  1469. case 'USER' :
  1470. if (! isset($source_data_array['language']))
  1471. {
  1472. $this->errors[] = '[language] not specified for ' . $frame_name;
  1473. }
  1474. elseif (in_array($frame_name . $source_data_array['language'], $PreviousFrames))
  1475. {
  1476. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Language (' . $source_data_array['language'] . ')';
  1477. }
  1478. else
  1479. {
  1480. $PreviousFrames[] = $frame_name . $source_data_array['language'];
  1481. }
  1482. break;
  1483. case 'USLT' :
  1484. case 'SYLT' :
  1485. case 'COMM' :
  1486. if (! isset($source_data_array['language']))
  1487. {
  1488. $this->errors[] = '[language] not specified for ' . $frame_name;
  1489. }
  1490. elseif (! isset($source_data_array['description']))
  1491. {
  1492. $this->errors[] = '[description] not specified for ' . $frame_name;
  1493. }
  1494. elseif (in_array($frame_name . $source_data_array['language'] . $source_data_array['description'], $PreviousFrames))
  1495. {
  1496. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Language + Description (' . $source_data_array['language'] . ' + ' . $source_data_array['description'] . ')';
  1497. }
  1498. else
  1499. {
  1500. $PreviousFrames[] = $frame_name . $source_data_array['language'] . $source_data_array['description'];
  1501. }
  1502. break;
  1503. case 'POPM' :
  1504. if (! isset($source_data_array['email']))
  1505. {
  1506. $this->errors[] = '[email] not specified for ' . $frame_name;
  1507. }
  1508. elseif (in_array($frame_name . $source_data_array['email'], $PreviousFrames))
  1509. {
  1510. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Email (' . $source_data_array['email'] . ')';
  1511. }
  1512. else
  1513. {
  1514. $PreviousFrames[] = $frame_name . $source_data_array['email'];
  1515. }
  1516. break;
  1517. case 'IPLS' :
  1518. case 'MCDI' :
  1519. case 'ETCO' :
  1520. case 'MLLT' :
  1521. case 'SYTC' :
  1522. case 'RVRB' :
  1523. case 'PCNT' :
  1524. case 'RBUF' :
  1525. case 'POSS' :
  1526. case 'OWNE' :
  1527. case 'SEEK' :
  1528. case 'ASPI' :
  1529. case 'RGAD' :
  1530. if (in_array($frame_name, $PreviousFrames))
  1531. {
  1532. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed';
  1533. }
  1534. else
  1535. {
  1536. $PreviousFrames[] = $frame_name;
  1537. }
  1538. break;
  1539. case 'LINK' :
  1540. // this isn't implemented quite right (yet) - it should check the target frame data for compliance
  1541. // but right now it just allows one linked frame of each type, to be safe.
  1542. if (! isset($source_data_array['frameid']))
  1543. {
  1544. $this->errors[] = '[frameid] not specified for ' . $frame_name;
  1545. }
  1546. elseif (in_array($frame_name . $source_data_array['frameid'], $PreviousFrames))
  1547. {
  1548. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same FrameID (' . $source_data_array['frameid'] . ')';
  1549. }
  1550. elseif (in_array($source_data_array['frameid'], $PreviousFrames))
  1551. {
  1552. // no links to singleton tags
  1553. $this->errors[] = 'Cannot specify a ' . $frame_name . ' tag to a singleton tag that already exists (' . $source_data_array['frameid'] . ')';
  1554. }
  1555. else
  1556. {
  1557. $PreviousFrames[] = $frame_name . $source_data_array['frameid']; // only one linked tag of this type
  1558. $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type
  1559. }
  1560. break;
  1561. case 'COMR' :
  1562. // There may be more than one 'commercial frame' in a tag, but no two may be identical
  1563. // Checking isn't implemented at all (yet) - just assumes that it's OK.
  1564. break;
  1565. case 'PRIV' :
  1566. case 'SIGN' :
  1567. if (! isset($source_data_array['ownerid']))
  1568. {
  1569. $this->errors[] = '[ownerid] not specified for ' . $frame_name;
  1570. }
  1571. elseif (! isset($source_data_array['data']))
  1572. {
  1573. $this->errors[] = '[data] not specified for ' . $frame_name;
  1574. }
  1575. elseif (in_array($frame_name . $source_data_array['ownerid'] . $source_data_array['data'], $PreviousFrames))
  1576. {
  1577. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same OwnerID + Data (' . $source_data_array['ownerid'] . ' + ' . $source_data_array['data'] . ')';
  1578. }
  1579. else
  1580. {
  1581. $PreviousFrames[] = $frame_name . $source_data_array['ownerid'] . $source_data_array['data'];
  1582. }
  1583. break;
  1584. default :
  1585. if (($frame_name{0} != 'T') && ($frame_name{0} != 'W'))
  1586. {
  1587. $this->errors[] = 'Frame not allowed in ID3v2.' . $this->majorversion . ': ' . $frame_name;
  1588. }
  1589. break;
  1590. }
  1591. }
  1592. elseif ($this->majorversion == 3)
  1593. {
  1594. switch ($frame_name)
  1595. {
  1596. case 'UFID' :
  1597. case 'AENC' :
  1598. case 'ENCR' :
  1599. case 'GRID' :
  1600. if (! isset($source_data_array['ownerid']))
  1601. {
  1602. $this->errors[] = '[ownerid] not specified for ' . $frame_name;
  1603. }
  1604. elseif (in_array($frame_name . $source_data_array['ownerid'], $PreviousFrames))
  1605. {
  1606. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same OwnerID (' . $source_data_array['ownerid'] . ')';
  1607. }
  1608. else
  1609. {
  1610. $PreviousFrames[] = $frame_name . $source_data_array['ownerid'];
  1611. }
  1612. break;
  1613. case 'TXXX' :
  1614. case 'WXXX' :
  1615. case 'APIC' :
  1616. case 'GEOB' :
  1617. if (! isset($source_data_array['description']))
  1618. {
  1619. $this->errors[] = '[description] not specified for ' . $frame_name;
  1620. }
  1621. elseif (in_array($frame_name . $source_data_array['description'], $PreviousFrames))
  1622. {
  1623. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Description (' . $source_data_array['description'] . ')';
  1624. }
  1625. else
  1626. {
  1627. $PreviousFrames[] = $frame_name . $source_data_array['description'];
  1628. }
  1629. break;
  1630. case 'USER' :
  1631. if (! isset($source_data_array['language']))
  1632. {
  1633. $this->errors[] = '[language] not specified for ' . $frame_name;
  1634. }
  1635. elseif (in_array($frame_name . $source_data_array['language'], $PreviousFrames))
  1636. {
  1637. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Language (' . $source_data_array['language'] . ')';
  1638. }
  1639. else
  1640. {
  1641. $PreviousFrames[] = $frame_name . $source_data_array['language'];
  1642. }
  1643. break;
  1644. case 'USLT' :
  1645. case 'SYLT' :
  1646. case 'COMM' :
  1647. if (! isset($source_data_array['language']))
  1648. {
  1649. $this->errors[] = '[language] not specified for ' . $frame_name;
  1650. }
  1651. elseif (! isset($source_data_array['description']))
  1652. {
  1653. $this->errors[] = '[description] not specified for ' . $frame_name;
  1654. }
  1655. elseif (in_array($frame_name . $source_data_array['language'] . $source_data_array['description'], $PreviousFrames))
  1656. {
  1657. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Language + Description (' . $source_data_array['language'] . ' + ' . $source_data_array['description'] . ')';
  1658. }
  1659. else
  1660. {
  1661. $PreviousFrames[] = $frame_name . $source_data_array['language'] . $source_data_array['description'];
  1662. }
  1663. break;
  1664. case 'POPM' :
  1665. if (! isset($source_data_array['email']))
  1666. {
  1667. $this->errors[] = '[email] not specified for ' . $frame_name;
  1668. }
  1669. elseif (in_array($frame_name . $source_data_array['email'], $PreviousFrames))
  1670. {
  1671. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Email (' . $source_data_array['email'] . ')';
  1672. }
  1673. else
  1674. {
  1675. $PreviousFrames[] = $frame_name . $source_data_array['email'];
  1676. }
  1677. break;
  1678. case 'IPLS' :
  1679. case 'MCDI' :
  1680. case 'ETCO' :
  1681. case 'MLLT' :
  1682. case 'SYTC' :
  1683. case 'RVAD' :
  1684. case 'EQUA' :
  1685. case 'RVRB' :
  1686. case 'PCNT' :
  1687. case 'RBUF' :
  1688. case 'POSS' :
  1689. case 'OWNE' :
  1690. case 'RGAD' :
  1691. if (in_array($frame_name, $PreviousFrames))
  1692. {
  1693. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed';
  1694. }
  1695. else
  1696. {
  1697. $PreviousFrames[] = $frame_name;
  1698. }
  1699. break;
  1700. case 'LINK' :
  1701. // this isn't implemented quite right (yet) - it should check the target frame data for compliance
  1702. // but right now it just allows one linked frame of each type, to be safe.
  1703. if (! isset($source_data_array['frameid']))
  1704. {
  1705. $this->errors[] = '[frameid] not specified for ' . $frame_name;
  1706. }
  1707. elseif (in_array($frame_name . $source_data_array['frameid'], $PreviousFrames))
  1708. {
  1709. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same FrameID (' . $source_data_array['frameid'] . ')';
  1710. }
  1711. elseif (in_array($source_data_array['frameid'], $PreviousFrames))
  1712. {
  1713. // no links to singleton tags
  1714. $this->errors[] = 'Cannot specify a ' . $frame_name . ' tag to a singleton tag that already exists (' . $source_data_array['frameid'] . ')';
  1715. }
  1716. else
  1717. {
  1718. $PreviousFrames[] = $frame_name . $source_data_array['frameid']; // only one linked tag of this type
  1719. $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type
  1720. }
  1721. break;
  1722. case 'COMR' :
  1723. // There may be more than one 'commercial frame' in a tag, but no two may be identical
  1724. // Checking isn't implemented at all (yet) - just assumes that it's OK.
  1725. break;
  1726. case 'PRIV' :
  1727. if (! isset($source_data_array['ownerid']))
  1728. {
  1729. $this->errors[] = '[ownerid] not specified for ' . $frame_name;
  1730. }
  1731. elseif (! isset($source_data_array['data']))
  1732. {
  1733. $this->errors[] = '[data] not specified for ' . $frame_name;
  1734. }
  1735. elseif (in_array($frame_name . $source_data_array['ownerid'] . $source_data_array['data'], $PreviousFrames))
  1736. {
  1737. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same OwnerID + Data (' . $source_data_array['ownerid'] . ' + ' . $source_data_array['data'] . ')';
  1738. }
  1739. else
  1740. {
  1741. $PreviousFrames[] = $frame_name . $source_data_array['ownerid'] . $source_data_array['data'];
  1742. }
  1743. break;
  1744. default :
  1745. if (($frame_name{0} != 'T') && ($frame_name{0} != 'W'))
  1746. {
  1747. $this->errors[] = 'Frame not allowed in ID3v2.' . $this->majorversion . ': ' . $frame_name;
  1748. }
  1749. break;
  1750. }
  1751. }
  1752. elseif ($this->majorversion == 2)
  1753. {
  1754. switch ($frame_name)
  1755. {
  1756. case 'UFI' :
  1757. case 'CRM' :
  1758. case 'CRA' :
  1759. if (! isset($source_data_array['ownerid']))
  1760. {
  1761. $this->errors[] = '[ownerid] not specified for ' . $frame_name;
  1762. }
  1763. elseif (in_array($frame_name . $source_data_array['ownerid'], $PreviousFrames))
  1764. {
  1765. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same OwnerID (' . $source_data_array['ownerid'] . ')';
  1766. }
  1767. else
  1768. {
  1769. $PreviousFrames[] = $frame_name . $source_data_array['ownerid'];
  1770. }
  1771. break;
  1772. case 'TXX' :
  1773. case 'WXX' :
  1774. case 'PIC' :
  1775. case 'GEO' :
  1776. if (! isset($source_data_array['description']))
  1777. {
  1778. $this->errors[] = '[description] not specified for ' . $frame_name;
  1779. }
  1780. elseif (in_array($frame_name . $source_data_array['description'], $PreviousFrames))
  1781. {
  1782. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Description (' . $source_data_array['description'] . ')';
  1783. }
  1784. else
  1785. {
  1786. $PreviousFrames[] = $frame_name . $source_data_array['description'];
  1787. }
  1788. break;
  1789. case 'ULT' :
  1790. case 'SLT' :
  1791. case 'COM' :
  1792. if (! isset($source_data_array['language']))
  1793. {
  1794. $this->errors[] = '[language] not specified for ' . $frame_name;
  1795. }
  1796. elseif (! isset($source_data_array['description']))
  1797. {
  1798. $this->errors[] = '[description] not specified for ' . $frame_name;
  1799. }
  1800. elseif (in_array($frame_name . $source_data_array['language'] . $source_data_array['description'], $PreviousFrames))
  1801. {
  1802. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Language + Description (' . $source_data_array['language'] . ' + ' . $source_data_array['description'] . ')';
  1803. }
  1804. else
  1805. {
  1806. $PreviousFrames[] = $frame_name . $source_data_array['language'] . $source_data_array['description'];
  1807. }
  1808. break;
  1809. case 'POP' :
  1810. if (! isset($source_data_array['email']))
  1811. {
  1812. $this->errors[] = '[email] not specified for ' . $frame_name;
  1813. }
  1814. elseif (in_array($frame_name . $source_data_array['email'], $PreviousFrames))
  1815. {
  1816. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Email (' . $source_data_array['email'] . ')';
  1817. }
  1818. else
  1819. {
  1820. $PreviousFrames[] = $frame_name . $source_data_array['email'];
  1821. }
  1822. break;
  1823. case 'IPL' :
  1824. case 'MCI' :
  1825. case 'ETC' :
  1826. case 'MLL' :
  1827. case 'STC' :
  1828. case 'RVA' :
  1829. case 'EQU' :
  1830. case 'REV' :
  1831. case 'CNT' :
  1832. case 'BUF' :
  1833. if (in_array($frame_name, $PreviousFrames))
  1834. {
  1835. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed';
  1836. }
  1837. else
  1838. {
  1839. $PreviousFrames[] = $frame_name;
  1840. }
  1841. break;
  1842. case 'LNK' :
  1843. // this isn't implemented quite right (yet) - it should check the target frame data for compliance
  1844. // but right now it just allows one linked frame of each type, to be safe.
  1845. if (! isset($source_data_array['frameid']))
  1846. {
  1847. $this->errors[] = '[frameid] not specified for ' . $frame_name;
  1848. }
  1849. elseif (in_array($frame_name . $source_data_array['frameid'], $PreviousFrames))
  1850. {
  1851. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same FrameID (' . $source_data_array['frameid'] . ')';
  1852. }
  1853. elseif (in_array($source_data_array['frameid'], $PreviousFrames))
  1854. {
  1855. // no links to singleton tags
  1856. $this->errors[] = 'Cannot specify a ' . $frame_name . ' tag to a singleton tag that already exists (' . $source_data_array['frameid'] . ')';
  1857. }
  1858. else
  1859. {
  1860. $PreviousFrames[] = $frame_name . $source_data_array['frameid']; // only one linked tag of this type
  1861. $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type
  1862. }
  1863. break;
  1864. default :
  1865. if (($frame_name{0} != 'T') && ($frame_name{0} != 'W'))
  1866. {
  1867. $this->errors[] = 'Frame not allowed in ID3v2.' . $this->majorversion . ': ' . $frame_name;
  1868. }
  1869. break;
  1870. }
  1871. }
  1872. if (! empty($this->errors))
  1873. {
  1874. return false;
  1875. }
  1876. return true;
  1877. }
  1878. function GenerateID3v2Tag($noerrorsonly = true)
  1879. {
  1880. $this->ID3v2FrameIsAllowed(null, ''); // clear static array in case this isn't the first call to $this->GenerateID3v2Tag()
  1881. $tagstring = '';
  1882. if (is_array($this->tag_data))
  1883. {
  1884. foreach ($this->tag_data as $frame_name => $frame_rawinputdata)
  1885. {
  1886. foreach ($frame_rawinputdata as $irrelevantindex => $source_data_array)
  1887. {
  1888. if (getid3_id3v2 :: IsValidID3v2FrameName($frame_name, $this->majorversion))
  1889. {
  1890. unset($frame_length);
  1891. unset($frame_flags);
  1892. $frame_data = false;
  1893. if ($this->ID3v2FrameIsAllowed($frame_name, $source_data_array))
  1894. {
  1895. if ($frame_data = $this->GenerateID3v2FrameData($frame_name, $source_data_array))
  1896. {
  1897. $FrameUnsynchronisation = false;
  1898. if ($this->majorversion >= 4)
  1899. {
  1900. // frame-level unsynchronisation
  1901. $unsynchdata = $frame_data;
  1902. if ($this->id3v2_use_unsynchronisation)
  1903. {
  1904. $unsynchdata = $this->Unsynchronise($frame_data);
  1905. }
  1906. if (strlen($unsynchdata) != strlen($frame_data))
  1907. {
  1908. // unsynchronisation needed
  1909. $FrameUnsynchronisation = true;
  1910. $frame_data = $unsynchdata;
  1911. if (isset($TagUnsynchronisation) && $TagUnsynchronisation === false)
  1912. {
  1913. // only set to true if ALL frames are unsynchronised
  1914. }
  1915. else
  1916. {
  1917. $TagUnsynchronisation = true;
  1918. }
  1919. }
  1920. else
  1921. {
  1922. if (isset($TagUnsynchronisation))
  1923. {
  1924. $TagUnsynchronisation = false;
  1925. }
  1926. }
  1927. unset($unsynchdata);
  1928. $frame_length = getid3_lib :: BigEndian2String(strlen($frame_data), 4, true);
  1929. }
  1930. else
  1931. {
  1932. $frame_length = getid3_lib :: BigEndian2String(strlen($frame_data), 4, false);
  1933. }
  1934. $frame_flags = $this->GenerateID3v2FrameFlags($this->ID3v2FrameFlagsLookupTagAlter($frame_name), $this->ID3v2FrameFlagsLookupFileAlter($frame_name), false, false, false, false, $FrameUnsynchronisation, false);
  1935. }
  1936. }
  1937. else
  1938. {
  1939. $this->errors[] = 'Frame "' . $frame_name . '" is NOT allowed';
  1940. }
  1941. if ($frame_data === false)
  1942. {
  1943. $this->errors[] = '$this->GenerateID3v2FrameData() failed for "' . $frame_name . '"';
  1944. if ($noerrorsonly)
  1945. {
  1946. return false;
  1947. }
  1948. else
  1949. {
  1950. unset($frame_name);
  1951. }
  1952. }
  1953. }
  1954. else
  1955. {
  1956. // ignore any invalid frame names, including 'title', 'header', etc
  1957. $this->warnings[] = 'Ignoring invalid ID3v2 frame type: "' . $frame_name . '"';
  1958. unset($frame_name);
  1959. unset($frame_length);
  1960. unset($frame_flags);
  1961. unset($frame_data);
  1962. }
  1963. if (isset($frame_name) && isset($frame_length) && isset($frame_flags) && isset($frame_data))
  1964. {
  1965. $tagstring .= $frame_name . $frame_length . $frame_flags . $frame_data;
  1966. }
  1967. }
  1968. }
  1969. if (! isset($TagUnsynchronisation))
  1970. {
  1971. $TagUnsynchronisation = false;
  1972. }
  1973. if (($this->majorversion <= 3) && $this->id3v2_use_unsynchronisation)
  1974. {
  1975. // tag-level unsynchronisation
  1976. $unsynchdata = $this->Unsynchronise($tagstring);
  1977. if (strlen($unsynchdata) != strlen($tagstring))
  1978. {
  1979. // unsynchronisation needed
  1980. $TagUnsynchronisation = true;
  1981. $tagstring = $unsynchdata;
  1982. }
  1983. }
  1984. while ($this->paddedlength < (strlen($tagstring) + getid3_id3v2 :: ID3v2HeaderLength($this->majorversion)))
  1985. {
  1986. $this->paddedlength += 1024;
  1987. }
  1988. $footer = false; // ID3v2 footers not yet supported in getID3()
  1989. if (! $footer && ($this->paddedlength > (strlen($tagstring) + getid3_id3v2 :: ID3v2HeaderLength($this->majorversion))))
  1990. {
  1991. // pad up to $paddedlength bytes if unpadded tag is shorter than $paddedlength
  1992. // "Furthermore it MUST NOT have any padding when a tag footer is added to the tag."
  1993. $tagstring .= @str_repeat("\x00", $this->paddedlength - strlen($tagstring) - getid3_id3v2 :: ID3v2HeaderLength($this->majorversion));
  1994. }
  1995. if ($this->id3v2_use_unsynchronisation && (substr($tagstring, strlen($tagstring) - 1, 1) == "\xFF"))
  1996. {
  1997. // special unsynchronisation case:
  1998. // if last byte == $FF then appended a $00
  1999. $TagUnsynchronisation = true;
  2000. $tagstring .= "\x00";
  2001. }
  2002. $tagheader = 'ID3';
  2003. $tagheader .= chr($this->majorversion);
  2004. $tagheader .= chr($this->minorversion);
  2005. $tagheader .= $this->GenerateID3v2TagFlags(array('unsynchronisation' => $TagUnsynchronisation));
  2006. $tagheader .= getid3_lib :: BigEndian2String(strlen($tagstring), 4, true);
  2007. return $tagheader . $tagstring;
  2008. }
  2009. $this->errors[] = 'tag_data is not an array in GenerateID3v2Tag()';
  2010. return false;
  2011. }
  2012. function ID3v2IsValidPriceString($pricestring)
  2013. {
  2014. if (getid3_id3v2 :: LanguageLookup(substr($pricestring, 0, 3), true) == '')
  2015. {
  2016. return false;
  2017. }
  2018. elseif (! $this->IsANumber(substr($pricestring, 3), true))
  2019. {
  2020. return false;
  2021. }
  2022. return true;
  2023. }
  2024. function ID3v2FrameFlagsLookupTagAlter($framename)
  2025. {
  2026. // unfinished
  2027. switch ($framename)
  2028. {
  2029. case 'RGAD' :
  2030. $allow = true;
  2031. default :
  2032. $allow = false;
  2033. break;
  2034. }
  2035. return $allow;
  2036. }
  2037. function ID3v2FrameFlagsLookupFileAlter($framename)
  2038. {
  2039. // unfinished
  2040. switch ($framename)
  2041. {
  2042. case 'RGAD' :
  2043. return false;
  2044. break;
  2045. default :
  2046. return false;
  2047. break;
  2048. }
  2049. }
  2050. function ID3v2IsValidETCOevent($eventid)
  2051. {
  2052. if (($eventid < 0) || ($eventid > 0xFF))
  2053. {
  2054. // outside range of 1 byte
  2055. return false;
  2056. }
  2057. elseif (($eventid >= 0xF0) && ($eventid <= 0xFC))
  2058. {
  2059. // reserved for future use
  2060. return false;
  2061. }
  2062. elseif (($eventid >= 0x17) && ($eventid <= 0xDF))
  2063. {
  2064. // reserved for future use
  2065. return false;
  2066. }
  2067. elseif (($eventid >= 0x0E) && ($eventid <= 0x16) && ($this->majorversion == 2))
  2068. {
  2069. // not defined in ID3v2.2
  2070. return false;
  2071. }
  2072. elseif (($eventid >= 0x15) && ($eventid <= 0x16) && ($this->majorversion == 3))
  2073. {
  2074. // not defined in ID3v2.3
  2075. return false;
  2076. }
  2077. return true;
  2078. }
  2079. function ID3v2IsValidSYLTtype($contenttype)
  2080. {
  2081. if (($contenttype >= 0) && ($contenttype <= 8) && ($this->majorversion == 4))
  2082. {
  2083. return true;
  2084. }
  2085. elseif (($contenttype >= 0) && ($contenttype <= 6) && ($this->majorversion == 3))
  2086. {
  2087. return true;
  2088. }
  2089. return false;
  2090. }
  2091. function ID3v2IsValidRVA2channeltype($channeltype)
  2092. {
  2093. if (($channeltype >= 0) && ($channeltype <= 8) && ($this->majorversion == 4))
  2094. {
  2095. return true;
  2096. }
  2097. return false;
  2098. }
  2099. function ID3v2IsValidAPICpicturetype($picturetype)
  2100. {
  2101. if (($picturetype >= 0) && ($picturetype <= 0x14) && ($this->majorversion >= 2) && ($this->majorversion <= 4))
  2102. {
  2103. return true;
  2104. }
  2105. return false;
  2106. }
  2107. function ID3v2IsValidAPICimageformat($imageformat)
  2108. {
  2109. if ($imageformat == '-->')
  2110. {
  2111. return true;
  2112. }
  2113. elseif ($this->majorversion == 2)
  2114. {
  2115. if ((strlen($imageformat) == 3) && ($imageformat == strtoupper($imageformat)))
  2116. {
  2117. return true;
  2118. }
  2119. }
  2120. elseif (($this->majorversion == 3) || ($this->majorversion == 4))
  2121. {
  2122. if ($this->IsValidMIMEstring($imageformat))
  2123. {
  2124. return true;
  2125. }
  2126. }
  2127. return false;
  2128. }
  2129. function ID3v2IsValidCOMRreceivedAs($receivedas)
  2130. {
  2131. if (($this->majorversion >= 3) && ($receivedas >= 0) && ($receivedas <= 8))
  2132. {
  2133. return true;
  2134. }
  2135. return false;
  2136. }
  2137. function ID3v2IsValidRGADname($RGADname)
  2138. {
  2139. if (($RGADname >= 0) && ($RGADname <= 2))
  2140. {
  2141. return true;
  2142. }
  2143. return false;
  2144. }
  2145. function ID3v2IsValidRGADoriginator($RGADoriginator)
  2146. {
  2147. if (($RGADoriginator >= 0) && ($RGADoriginator <= 3))
  2148. {
  2149. return true;
  2150. }
  2151. return false;
  2152. }
  2153. function ID3v2IsValidTextEncoding($textencodingbyte)
  2154. {
  2155. static $ID3v2IsValidTextEncoding_cache = array(2 => array(true, true), 3 => array(true, true),
  2156. 4 => array(true, true, true, true));
  2157. return isset($ID3v2IsValidTextEncoding_cache[$this->majorversion][$textencodingbyte]);
  2158. }
  2159. function Unsynchronise($data)
  2160. {
  2161. // Whenever a false synchronisation is found within the tag, one zeroed
  2162. // byte is inserted after the first false synchronisation byte. The
  2163. // format of a correct sync that should be altered by ID3 encoders is as
  2164. // follows:
  2165. // %11111111 111xxxxx
  2166. // And should be replaced with:
  2167. // %11111111 00000000 111xxxxx
  2168. // This has the side effect that all $FF 00 combinations have to be
  2169. // altered, so they won't be affected by the decoding process. Therefore
  2170. // all the $FF 00 combinations have to be replaced with the $FF 00 00
  2171. // combination during the unsynchronisation.
  2172. $data = str_replace("\xFF\x00", "\xFF\x00\x00", $data);
  2173. $unsyncheddata = '';
  2174. $datalength = strlen($data);
  2175. for($i = 0; $i < $datalength; $i ++)
  2176. {
  2177. $thischar = $data{$i};
  2178. $unsyncheddata .= $thischar;
  2179. if ($thischar == "\xFF")
  2180. {
  2181. $nextchar = ord($data{$i + 1});
  2182. if (($nextchar & 0xE0) == 0xE0)
  2183. {
  2184. // previous byte = 11111111, this byte = 111?????
  2185. $unsyncheddata .= "\x00";
  2186. }
  2187. }
  2188. }
  2189. return $unsyncheddata;
  2190. }
  2191. function is_hash($var)
  2192. {
  2193. // written by dev-null�christophe*vg
  2194. // taken from http://www.php.net/manual/en/function.array-merge-recursive.php
  2195. if (is_array($var))
  2196. {
  2197. $keys = array_keys($var);
  2198. $all_num = true;
  2199. for($i = 0; $i < count($keys); $i ++)
  2200. {
  2201. if (is_string($keys[$i]))
  2202. {
  2203. return true;
  2204. }
  2205. }
  2206. }
  2207. return false;
  2208. }
  2209. function array_join_merge($arr1, $arr2)
  2210. {
  2211. // written by dev-null�christophe*vg
  2212. // taken from http://www.php.net/manual/en/function.array-merge-recursive.php
  2213. if (is_array($arr1) && is_array($arr2))
  2214. {
  2215. // the same -> merge
  2216. $new_array = array();
  2217. if ($this->is_hash($arr1) && $this->is_hash($arr2))
  2218. {
  2219. // hashes -> merge based on keys
  2220. $keys = array_merge(array_keys($arr1), array_keys($arr2));
  2221. foreach ($keys as $key)
  2222. {
  2223. $new_array[$key] = $this->array_join_merge(@$arr1[$key], @$arr2[$key]);
  2224. }
  2225. }
  2226. else
  2227. {
  2228. // two real arrays -> merge
  2229. $new_array = array_reverse(array_unique(array_reverse(array_merge($arr1, $arr2))));
  2230. }
  2231. return $new_array;
  2232. }
  2233. else
  2234. {
  2235. // not the same ... take new one if defined, else the old one stays
  2236. return $arr2 ? $arr2 : $arr1;
  2237. }
  2238. }
  2239. function IsValidMIMEstring($mimestring)
  2240. {
  2241. if ((strlen($mimestring) >= 3) && (strpos($mimestring, '/') > 0) && (strpos($mimestring, '/') < (strlen($mimestring) - 1)))
  2242. {
  2243. return true;
  2244. }
  2245. return false;
  2246. }
  2247. function IsWithinBitRange($number, $maxbits, $signed = false)
  2248. {
  2249. if ($signed)
  2250. {
  2251. if (($number > (0 - pow(2, $maxbits - 1))) && ($number <= pow(2, $maxbits - 1)))
  2252. {
  2253. return true;
  2254. }
  2255. }
  2256. else
  2257. {
  2258. if (($number >= 0) && ($number <= pow(2, $maxbits)))
  2259. {
  2260. return true;
  2261. }
  2262. }
  2263. return false;
  2264. }
  2265. function safe_parse_url($url)
  2266. {
  2267. $parts = @parse_url($url);
  2268. $parts['scheme'] = (isset($parts['scheme']) ? $parts['scheme'] : '');
  2269. $parts['host'] = (isset($parts['host']) ? $parts['host'] : '');
  2270. $parts['user'] = (isset($parts['user']) ? $parts['user'] : '');
  2271. $parts['pass'] = (isset($parts['pass']) ? $parts['pass'] : '');
  2272. $parts['path'] = (isset($parts['path']) ? $parts['path'] : '');
  2273. $parts['query'] = (isset($parts['query']) ? $parts['query'] : '');
  2274. return $parts;
  2275. }
  2276. function IsValidURL($url, $allowUserPass = false)
  2277. {
  2278. if ($url == '')
  2279. {
  2280. return false;
  2281. }
  2282. if ($allowUserPass !== true)
  2283. {
  2284. if (strstr($url, '@'))
  2285. {
  2286. // in the format http://user:pass@example.com or http://user@example.com
  2287. // but could easily be somebody incorrectly entering an email address in place of a URL
  2288. return false;
  2289. }
  2290. }
  2291. if ($parts = $this->safe_parse_url($url))
  2292. {
  2293. if (($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') && ($parts['scheme'] != 'ftp') && ($parts['scheme'] != 'gopher'))
  2294. {
  2295. return false;
  2296. }
  2297. elseif (! eregi("^[[:alnum:]]([-.]?[0-9a-z])*\.[a-z]{2,3}$", $parts['host'], $regs) && ! IsValidDottedIP($parts['host']))
  2298. {
  2299. return false;
  2300. }
  2301. elseif (! eregi("^([[:alnum:]-]|[\_])*$", $parts['user'], $regs))
  2302. {
  2303. return false;
  2304. }
  2305. elseif (! eregi("^([[:alnum:]-]|[\_])*$", $parts['pass'], $regs))
  2306. {
  2307. return false;
  2308. }
  2309. elseif (! eregi("^[[:alnum:]/_\.@~-]*$", $parts['path'], $regs))
  2310. {
  2311. return false;
  2312. }
  2313. elseif (! eregi("^[[:alnum:]?&=+:;_()%#/,\.-]*$", $parts['query'], $regs))
  2314. {
  2315. return false;
  2316. }
  2317. else
  2318. {
  2319. return true;
  2320. }
  2321. }
  2322. return false;
  2323. }
  2324. function ID3v2ShortFrameNameLookup($majorversion, $long_description)
  2325. {
  2326. $long_description = str_replace(' ', '_', strtolower(trim($long_description)));
  2327. static $ID3v2ShortFrameNameLookup = array();
  2328. if (empty($ID3v2ShortFrameNameLookup))
  2329. {
  2330. // The following are unique to ID3v2.2
  2331. $ID3v2ShortFrameNameLookup[2]['comment'] = 'COM';
  2332. $ID3v2ShortFrameNameLookup[2]['album'] = 'TAL';
  2333. $ID3v2ShortFrameNameLookup[2]['beats_per_minute'] = 'TBP';
  2334. $ID3v2ShortFrameNameLookup[2]['composer'] = 'TCM';
  2335. $ID3v2ShortFrameNameLookup[2]['genre'] = 'TCO';
  2336. $ID3v2ShortFrameNameLookup[2]['itunescompilation'] = 'TCP';
  2337. $ID3v2ShortFrameNameLookup[2]['copyright'] = 'TCR';
  2338. $ID3v2ShortFrameNameLookup[2]['encoded_by'] = 'TEN';
  2339. $ID3v2ShortFrameNameLookup[2]['language'] = 'TLA';
  2340. $ID3v2ShortFrameNameLookup[2]['length'] = 'TLE';
  2341. $ID3v2ShortFrameNameLookup[2]['original_artist'] = 'TOA';
  2342. $ID3v2ShortFrameNameLookup[2]['original_filename'] = 'TOF';
  2343. $ID3v2ShortFrameNameLookup[2]['original_lyricist'] = 'TOL';
  2344. $ID3v2ShortFrameNameLookup[2]['original_album_title'] = 'TOT';
  2345. $ID3v2ShortFrameNameLookup[2]['artist'] = 'TP1';
  2346. $ID3v2ShortFrameNameLookup[2]['band'] = 'TP2';
  2347. $ID3v2ShortFrameNameLookup[2]['conductor'] = 'TP3';
  2348. $ID3v2ShortFrameNameLookup[2]['remixer'] = 'TP4';
  2349. $ID3v2ShortFrameNameLookup[2]['publisher'] = 'TPB';
  2350. $ID3v2ShortFrameNameLookup[2]['isrc'] = 'TRC';
  2351. $ID3v2ShortFrameNameLookup[2]['tracknumber'] = 'TRK';
  2352. $ID3v2ShortFrameNameLookup[2]['size'] = 'TSI';
  2353. $ID3v2ShortFrameNameLookup[2]['encoder_settings'] = 'TSS';
  2354. $ID3v2ShortFrameNameLookup[2]['description'] = 'TT1';
  2355. $ID3v2ShortFrameNameLookup[2]['title'] = 'TT2';
  2356. $ID3v2ShortFrameNameLookup[2]['subtitle'] = 'TT3';
  2357. $ID3v2ShortFrameNameLookup[2]['lyricist'] = 'TXT';
  2358. $ID3v2ShortFrameNameLookup[2]['user_text'] = 'TXX';
  2359. $ID3v2ShortFrameNameLookup[2]['year'] = 'TYE';
  2360. $ID3v2ShortFrameNameLookup[2]['unique_file_identifier'] = 'UFI';
  2361. $ID3v2ShortFrameNameLookup[2]['unsynchronised_lyrics'] = 'ULT';
  2362. $ID3v2ShortFrameNameLookup[2]['url_file'] = 'WAF';
  2363. $ID3v2ShortFrameNameLookup[2]['url_artist'] = 'WAR';
  2364. $ID3v2ShortFrameNameLookup[2]['url_source'] = 'WAS';
  2365. $ID3v2ShortFrameNameLookup[2]['copyright_information'] = 'WCP';
  2366. $ID3v2ShortFrameNameLookup[2]['url_publisher'] = 'WPB';
  2367. $ID3v2ShortFrameNameLookup[2]['url_user'] = 'WXX';
  2368. // The following are common to ID3v2.3 and ID3v2.4
  2369. $ID3v2ShortFrameNameLookup[3]['audio_encryption'] = 'AENC';
  2370. $ID3v2ShortFrameNameLookup[3]['attached_picture'] = 'APIC';
  2371. $ID3v2ShortFrameNameLookup[3]['comment'] = 'COMM';
  2372. $ID3v2ShortFrameNameLookup[3]['commercial'] = 'COMR';
  2373. $ID3v2ShortFrameNameLookup[3]['encryption_method_registration'] = 'ENCR';
  2374. $ID3v2ShortFrameNameLookup[3]['event_timing_codes'] = 'ETCO';
  2375. $ID3v2ShortFrameNameLookup[3]['general_encapsulated_object'] = 'GEOB';
  2376. $ID3v2ShortFrameNameLookup[3]['group_identification_registration'] = 'GRID';
  2377. $ID3v2ShortFrameNameLookup[3]['linked_information'] = 'LINK';
  2378. $ID3v2ShortFrameNameLookup[3]['music_cd_identifier'] = 'MCDI';
  2379. $ID3v2ShortFrameNameLookup[3]['mpeg_location_lookup_table'] = 'MLLT';
  2380. $ID3v2ShortFrameNameLookup[3]['ownership'] = 'OWNE';
  2381. $ID3v2ShortFrameNameLookup[3]['play_counter'] = 'PCNT';
  2382. $ID3v2ShortFrameNameLookup[3]['popularimeter'] = 'POPM';
  2383. $ID3v2ShortFrameNameLookup[3]['position_synchronisation'] = 'POSS';
  2384. $ID3v2ShortFrameNameLookup[3]['private'] = 'PRIV';
  2385. $ID3v2ShortFrameNameLookup[3]['recommended_buffer_size'] = 'RBUF';
  2386. $ID3v2ShortFrameNameLookup[3]['reverb'] = 'RVRB';
  2387. $ID3v2ShortFrameNameLookup[3]['synchronised_lyrics'] = 'SYLT';
  2388. $ID3v2ShortFrameNameLookup[3]['synchronised_tempo_codes'] = 'SYTC';
  2389. $ID3v2ShortFrameNameLookup[3]['album'] = 'TALB';
  2390. $ID3v2ShortFrameNameLookup[3]['beats_per_minute'] = 'TBPM';
  2391. $ID3v2ShortFrameNameLookup[3]['itunescompilation'] = 'TCMP';
  2392. $ID3v2ShortFrameNameLookup[3]['composer'] = 'TCOM';
  2393. $ID3v2ShortFrameNameLookup[3]['genre'] = 'TCON';
  2394. $ID3v2ShortFrameNameLookup[3]['copyright'] = 'TCOP';
  2395. $ID3v2ShortFrameNameLookup[3]['playlist_delay'] = 'TDLY';
  2396. $ID3v2ShortFrameNameLookup[3]['encoded_by'] = 'TENC';
  2397. $ID3v2ShortFrameNameLookup[3]['lyricist'] = 'TEXT';
  2398. $ID3v2ShortFrameNameLookup[3]['file_type'] = 'TFLT';
  2399. $ID3v2ShortFrameNameLookup[3]['content_group_description'] = 'TIT1';
  2400. $ID3v2ShortFrameNameLookup[3]['title'] = 'TIT2';
  2401. $ID3v2ShortFrameNameLookup[3]['subtitle'] = 'TIT3';
  2402. $ID3v2ShortFrameNameLookup[3]['initial_key'] = 'TKEY';
  2403. $ID3v2ShortFrameNameLookup[3]['language'] = 'TLAN';
  2404. $ID3v2ShortFrameNameLookup[3]['length'] = 'TLEN';
  2405. $ID3v2ShortFrameNameLookup[3]['media_type'] = 'TMED';
  2406. $ID3v2ShortFrameNameLookup[3]['original_album_title'] = 'TOAL';
  2407. $ID3v2ShortFrameNameLookup[3]['original_filename'] = 'TOFN';
  2408. $ID3v2ShortFrameNameLookup[3]['original_lyricist'] = 'TOLY';
  2409. $ID3v2ShortFrameNameLookup[3]['original_artist'] = 'TOPE';
  2410. $ID3v2ShortFrameNameLookup[3]['file_owner'] = 'TOWN';
  2411. $ID3v2ShortFrameNameLookup[3]['artist'] = 'TPE1';
  2412. $ID3v2ShortFrameNameLookup[3]['band'] = 'TPE2';
  2413. $ID3v2ShortFrameNameLookup[3]['conductor'] = 'TPE3';
  2414. $ID3v2ShortFrameNameLookup[3]['remixer'] = 'TPE4';
  2415. $ID3v2ShortFrameNameLookup[3]['part_of_a_set'] = 'TPOS';
  2416. $ID3v2ShortFrameNameLookup[3]['publisher'] = 'TPUB';
  2417. $ID3v2ShortFrameNameLookup[3]['tracknumber'] = 'TRCK';
  2418. $ID3v2ShortFrameNameLookup[3]['internet_radio_station_name'] = 'TRSN';
  2419. $ID3v2ShortFrameNameLookup[3]['internet_radio_station_owner'] = 'TRSO';
  2420. $ID3v2ShortFrameNameLookup[3]['isrc'] = 'TSRC';
  2421. $ID3v2ShortFrameNameLookup[3]['encoder_settings'] = 'TSSE';
  2422. $ID3v2ShortFrameNameLookup[3]['user_text'] = 'TXXX';
  2423. $ID3v2ShortFrameNameLookup[3]['unique_file_identifier'] = 'UFID';
  2424. $ID3v2ShortFrameNameLookup[3]['terms_of_use'] = 'USER';
  2425. $ID3v2ShortFrameNameLookup[3]['unsynchronised_lyrics'] = 'USLT';
  2426. $ID3v2ShortFrameNameLookup[3]['commercial'] = 'WCOM';
  2427. $ID3v2ShortFrameNameLookup[3]['copyright_information'] = 'WCOP';
  2428. $ID3v2ShortFrameNameLookup[3]['url_file'] = 'WOAF';
  2429. $ID3v2ShortFrameNameLookup[3]['url_artist'] = 'WOAR';
  2430. $ID3v2ShortFrameNameLookup[3]['url_source'] = 'WOAS';
  2431. $ID3v2ShortFrameNameLookup[3]['url_station'] = 'WORS';
  2432. $ID3v2ShortFrameNameLookup[3]['payment'] = 'WPAY';
  2433. $ID3v2ShortFrameNameLookup[3]['url_publisher'] = 'WPUB';
  2434. $ID3v2ShortFrameNameLookup[3]['url_user'] = 'WXXX';
  2435. // The above are common to ID3v2.3 and ID3v2.4
  2436. // so copy them to ID3v2.4 before adding specifics for 2.3 and 2.4
  2437. $ID3v2ShortFrameNameLookup[4] = $ID3v2ShortFrameNameLookup[3];
  2438. // The following are unique to ID3v2.3
  2439. $ID3v2ShortFrameNameLookup[3]['equalisation'] = 'EQUA';
  2440. $ID3v2ShortFrameNameLookup[3]['involved_people_list'] = 'IPLS';
  2441. $ID3v2ShortFrameNameLookup[3]['relative_volume_adjustment'] = 'RVAD';
  2442. $ID3v2ShortFrameNameLookup[3]['date'] = 'TDAT';
  2443. $ID3v2ShortFrameNameLookup[3]['time'] = 'TIME';
  2444. $ID3v2ShortFrameNameLookup[3]['original_release_year'] = 'TORY';
  2445. $ID3v2ShortFrameNameLookup[3]['recording_dates'] = 'TRDA';
  2446. $ID3v2ShortFrameNameLookup[3]['size'] = 'TSIZ';
  2447. $ID3v2ShortFrameNameLookup[3]['year'] = 'TYER';
  2448. // The following are unique to ID3v2.4
  2449. $ID3v2ShortFrameNameLookup[4]['audio_seek_point_index'] = 'ASPI';
  2450. $ID3v2ShortFrameNameLookup[4]['equalisation'] = 'EQU2';
  2451. $ID3v2ShortFrameNameLookup[4]['relative_volume_adjustment'] = 'RVA2';
  2452. $ID3v2ShortFrameNameLookup[4]['seek'] = 'SEEK';
  2453. $ID3v2ShortFrameNameLookup[4]['signature'] = 'SIGN';
  2454. $ID3v2ShortFrameNameLookup[4]['encoding_time'] = 'TDEN';
  2455. $ID3v2ShortFrameNameLookup[4]['original_release_time'] = 'TDOR';
  2456. $ID3v2ShortFrameNameLookup[4]['recording_time'] = 'TDRC';
  2457. $ID3v2ShortFrameNameLookup[4]['release_time'] = 'TDRL';
  2458. $ID3v2ShortFrameNameLookup[4]['tagging_time'] = 'TDTG';
  2459. $ID3v2ShortFrameNameLookup[4]['involved_people_list'] = 'TIPL';
  2460. $ID3v2ShortFrameNameLookup[4]['musician_credits_list'] = 'TMCL';
  2461. $ID3v2ShortFrameNameLookup[4]['mood'] = 'TMOO';
  2462. $ID3v2ShortFrameNameLookup[4]['produced_notice'] = 'TPRO';
  2463. $ID3v2ShortFrameNameLookup[4]['album_sort_order'] = 'TSOA';
  2464. $ID3v2ShortFrameNameLookup[4]['performer_sort_order'] = 'TSOP';
  2465. $ID3v2ShortFrameNameLookup[4]['title_sort_order'] = 'TSOT';
  2466. $ID3v2ShortFrameNameLookup[4]['set_subtitle'] = 'TSST';
  2467. }
  2468. return @$ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)];
  2469. }
  2470. }
  2471. ?>