PageRenderTime 41ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/common/libraries/plugin/osflvplayer/flash/write.id3v2.php

https://bitbucket.org/renaatdemuynck/chamilo
PHP | 2618 lines | 2139 code | 167 blank | 312 comment | 336 complexity | 8e5b315522ac2a979e6acaa5c1340dc5 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 getid3_write_id3v2()
  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 ($this->merge_existing_data)
  42. {
  43. // merge with existing data
  44. if (! empty($OldThisFileInfo['id3v2']))
  45. {
  46. $this->tag_data = $this->array_join_merge($OldThisFileInfo['id3v2'], $this->tag_data);
  47. }
  48. }
  49. $this->paddedlength = max(@$OldThisFileInfo['id3v2']['headerlength'], $this->paddedlength);
  50. if ($NewID3v2Tag = $this->GenerateID3v2Tag())
  51. {
  52. if (file_exists($this->filename) && is_writeable($this->filename) && isset($OldThisFileInfo['id3v2']['headerlength']) && ($OldThisFileInfo['id3v2']['headerlength'] == strlen($NewID3v2Tag)))
  53. {
  54. // best and fastest method - insert-overwrite existing tag (padded to length of old tag if neccesary)
  55. if (file_exists($this->filename))
  56. {
  57. ob_start();
  58. if ($fp = fopen($this->filename, 'r+b'))
  59. {
  60. rewind($fp);
  61. fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag));
  62. fclose($fp);
  63. }
  64. else
  65. {
  66. $this->errors[] = 'Could not open ' . $this->filename . ' mode "r+b" - ' . strip_tags(ob_get_contents());
  67. }
  68. ob_end_clean();
  69. }
  70. else
  71. {
  72. ob_start();
  73. if ($fp = fopen($this->filename, 'wb'))
  74. {
  75. rewind($fp);
  76. fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag));
  77. fclose($fp);
  78. }
  79. else
  80. {
  81. $this->errors[] = 'Could not open ' . $this->filename . ' mode "wb" - ' . strip_tags(ob_get_contents());
  82. }
  83. ob_end_clean();
  84. }
  85. }
  86. else
  87. {
  88. if ($tempfilename = tempnam('*', 'getID3'))
  89. {
  90. ob_start();
  91. if ($fp_source = fopen($this->filename, 'rb'))
  92. {
  93. if ($fp_temp = fopen($tempfilename, 'wb'))
  94. {
  95. fwrite($fp_temp, $NewID3v2Tag, strlen($NewID3v2Tag));
  96. rewind($fp_source);
  97. if (! empty($OldThisFileInfo['avdataoffset']))
  98. {
  99. fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
  100. }
  101. while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE))
  102. {
  103. fwrite($fp_temp, $buffer, strlen($buffer));
  104. }
  105. fclose($fp_temp);
  106. fclose($fp_source);
  107. copy($tempfilename, $this->filename);
  108. unlink($tempfilename);
  109. ob_end_clean();
  110. return true;
  111. }
  112. else
  113. {
  114. $this->errors[] = 'Could not open ' . $tempfilename . ' mode "wb" - ' . strip_tags(ob_get_contents());
  115. }
  116. fclose($fp_source);
  117. }
  118. else
  119. {
  120. $this->errors[] = 'Could not open ' . $this->filename . ' mode "rb" - ' . strip_tags(ob_get_contents());
  121. }
  122. ob_end_clean();
  123. }
  124. return false;
  125. }
  126. }
  127. else
  128. {
  129. $this->errors[] = '$this->GenerateID3v2Tag() failed';
  130. }
  131. if (! empty($this->errors))
  132. {
  133. return false;
  134. }
  135. return true;
  136. }
  137. else
  138. {
  139. $this->errors[] = '!is_writeable(' . $this->filename . ')';
  140. }
  141. return false;
  142. }
  143. function RemoveID3v2()
  144. {
  145. // File MUST be writeable - CHMOD(646) at least. It's best if the
  146. // directory is also writeable, because that method is both faster and less susceptible to errors.
  147. if (is_writeable(dirname($this->filename)))
  148. {
  149. // preferred method - only one copying operation, minimal chance of corrupting
  150. // original file if script is interrupted, but required directory to be writeable
  151. if ($fp_source = @fopen($this->filename, 'rb'))
  152. {
  153. // Initialize getID3 engine
  154. $getID3 = new getID3();
  155. $OldThisFileInfo = $getID3->analyze($this->filename);
  156. rewind($fp_source);
  157. if ($OldThisFileInfo['avdataoffset'] !== false)
  158. {
  159. fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
  160. }
  161. if ($fp_temp = @fopen($this->filename . 'getid3tmp', 'w+b'))
  162. {
  163. while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE))
  164. {
  165. fwrite($fp_temp, $buffer, strlen($buffer));
  166. }
  167. fclose($fp_temp);
  168. }
  169. else
  170. {
  171. $this->errors[] = 'Could not open ' . $this->filename . 'getid3tmp mode "w+b"';
  172. }
  173. fclose($fp_source);
  174. }
  175. else
  176. {
  177. $this->errors[] = 'Could not open ' . $this->filename . ' mode "rb"';
  178. }
  179. if (file_exists($this->filename))
  180. {
  181. unlink($this->filename);
  182. }
  183. rename($this->filename . 'getid3tmp', $this->filename);
  184. }
  185. elseif (is_writable($this->filename))
  186. {
  187. // less desirable alternate method - double-copies the file, overwrites original file
  188. // and could corrupt source file if the script is interrupted or an error occurs.
  189. if ($fp_source = @fopen($this->filename, 'rb'))
  190. {
  191. // Initialize getID3 engine
  192. $getID3 = new getID3();
  193. $OldThisFileInfo = $getID3->analyze($this->filename);
  194. rewind($fp_source);
  195. if ($OldThisFileInfo['avdataoffset'] !== false)
  196. {
  197. fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
  198. }
  199. if ($fp_temp = tmpfile())
  200. {
  201. while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE))
  202. {
  203. fwrite($fp_temp, $buffer, strlen($buffer));
  204. }
  205. fclose($fp_source);
  206. if ($fp_source = @fopen($this->filename, 'wb'))
  207. {
  208. rewind($fp_temp);
  209. while ($buffer = fread($fp_temp, GETID3_FREAD_BUFFER_SIZE))
  210. {
  211. fwrite($fp_source, $buffer, strlen($buffer));
  212. }
  213. fseek($fp_temp, - 128, SEEK_END);
  214. fclose($fp_source);
  215. }
  216. else
  217. {
  218. $this->errors[] = 'Could not open ' . $this->filename . ' mode "wb"';
  219. }
  220. fclose($fp_temp);
  221. }
  222. else
  223. {
  224. $this->errors[] = 'Could not create tmpfile()';
  225. }
  226. }
  227. else
  228. {
  229. $this->errors[] = 'Could not open ' . $this->filename . ' mode "rb"';
  230. }
  231. }
  232. else
  233. {
  234. $this->errors[] = 'Directory and file both not writeable';
  235. }
  236. if (! empty($this->errors))
  237. {
  238. return false;
  239. }
  240. return true;
  241. }
  242. function GenerateID3v2TagFlags($flags)
  243. {
  244. switch ($this->majorversion)
  245. {
  246. case 4 :
  247. // %abcd0000
  248. $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
  249. $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header
  250. $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator
  251. $flag .= (@$flags['footer'] ? '1' : '0'); // d - Footer present
  252. $flag .= '0000';
  253. break;
  254. case 3 :
  255. // %abc00000
  256. $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
  257. $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header
  258. $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator
  259. $flag .= '00000';
  260. break;
  261. case 2 :
  262. // %ab000000
  263. $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
  264. $flag .= (@$flags['compression'] ? '1' : '0'); // b - Compression
  265. $flag .= '000000';
  266. break;
  267. default :
  268. return false;
  269. break;
  270. }
  271. return chr(bindec($flag));
  272. }
  273. function GenerateID3v2FrameFlags($TagAlter = false, $FileAlter = false, $ReadOnly = false, $Compression = false, $Encryption = false, $GroupingIdentity = false, $Unsynchronisation = false, $DataLengthIndicator = false)
  274. {
  275. switch ($this->majorversion)
  276. {
  277. case 4 :
  278. // %0abc0000 %0h00kmnp
  279. $flag1 = '0';
  280. $flag1 .= $TagAlter ? '1' : '0'; // a - Tag alter preservation (true == discard)
  281. $flag1 .= $FileAlter ? '1' : '0'; // b - File alter preservation (true == discard)
  282. $flag1 .= $ReadOnly ? '1' : '0'; // c - Read only (true == read only)
  283. $flag1 .= '0000';
  284. $flag2 = '0';
  285. $flag2 .= $GroupingIdentity ? '1' : '0'; // h - Grouping identity (true == contains group information)
  286. $flag2 .= '00';
  287. $flag2 .= $Compression ? '1' : '0'; // k - Compression (true == compressed)
  288. $flag2 .= $Encryption ? '1' : '0'; // m - Encryption (true == encrypted)
  289. $flag2 .= $Unsynchronisation ? '1' : '0'; // n - Unsynchronisation (true == unsynchronised)
  290. $flag2 .= $DataLengthIndicator ? '1' : '0'; // p - Data length indicator (true == data length indicator added)
  291. break;
  292. case 3 :
  293. // %abc00000 %ijk00000
  294. $flag1 = $TagAlter ? '1' : '0'; // a - Tag alter preservation (true == discard)
  295. $flag1 .= $FileAlter ? '1' : '0'; // b - File alter preservation (true == discard)
  296. $flag1 .= $ReadOnly ? '1' : '0'; // c - Read only (true == read only)
  297. $flag1 .= '00000';
  298. $flag2 = $Compression ? '1' : '0'; // i - Compression (true == compressed)
  299. $flag2 .= $Encryption ? '1' : '0'; // j - Encryption (true == encrypted)
  300. $flag2 .= $GroupingIdentity ? '1' : '0'; // k - Grouping identity (true == contains group information)
  301. $flag2 .= '00000';
  302. break;
  303. default :
  304. return false;
  305. break;
  306. }
  307. return chr(bindec($flag1)) . chr(bindec($flag2));
  308. }
  309. function GenerateID3v2FrameData($frame_name, $source_data_array)
  310. {
  311. if (! getid3_id3v2 :: IsValidID3v2FrameName($frame_name, $this->majorversion))
  312. {
  313. return false;
  314. }
  315. $framedata = '';
  316. if (($this->majorversion < 3) || ($this->majorversion > 4))
  317. {
  318. $this->errors[] = 'Only ID3v2.3 and ID3v2.4 are supported in GenerateID3v2FrameData()';
  319. }
  320. else
  321. { // $this->majorversion 3 or 4
  322. switch ($frame_name)
  323. {
  324. case 'UFID' :
  325. // 4.1 UFID Unique file identifier
  326. // Owner identifier <text string> $00
  327. // Identifier <up to 64 bytes binary data>
  328. if (strlen($source_data_array['data']) > 64)
  329. {
  330. $this->errors[] = 'Identifier not allowed to be longer than 64 bytes in ' . $frame_name . ' (supplied data was ' . strlen($source_data_array['data']) . ' bytes long)';
  331. }
  332. else
  333. {
  334. $framedata .= str_replace("\x00", '', $source_data_array['ownerid']) . "\x00";
  335. $framedata .= substr($source_data_array['data'], 0, 64); // max 64 bytes - truncate anything longer
  336. }
  337. break;
  338. case 'TXXX' :
  339. // 4.2.2 TXXX User defined text information frame
  340. // Text encoding $xx
  341. // Description <text string according to encoding> $00 (00)
  342. // Value <text string according to encoding>
  343. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  344. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion))
  345. {
  346. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  347. }
  348. else
  349. {
  350. $framedata .= chr($source_data_array['encodingid']);
  351. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  352. $framedata .= $source_data_array['data'];
  353. }
  354. break;
  355. case 'WXXX' :
  356. // 4.3.2 WXXX User defined URL link frame
  357. // Text encoding $xx
  358. // Description <text string according to encoding> $00 (00)
  359. // URL <text string>
  360. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  361. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion))
  362. {
  363. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  364. }
  365. elseif (! isset($source_data_array['data']) || ! $this->IsValidURL($source_data_array['data'], false, false))
  366. {
  367. //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
  368. // probably should be an error, need to rewrite IsValidURL() to handle other encodings
  369. $this->warnings[] = 'Invalid URL in ' . $frame_name . ' (' . $source_data_array['data'] . ')';
  370. }
  371. else
  372. {
  373. $framedata .= chr($source_data_array['encodingid']);
  374. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  375. $framedata .= $source_data_array['data'];
  376. }
  377. break;
  378. case 'IPLS' :
  379. // 4.4 IPLS Involved people list (ID3v2.3 only)
  380. // Text encoding $xx
  381. // People list strings <textstrings>
  382. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  383. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion))
  384. {
  385. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  386. }
  387. else
  388. {
  389. $framedata .= chr($source_data_array['encodingid']);
  390. $framedata .= $source_data_array['data'];
  391. }
  392. break;
  393. case 'MCDI' :
  394. // 4.4 MCDI Music CD identifier
  395. // CD TOC <binary data>
  396. $framedata .= $source_data_array['data'];
  397. break;
  398. case 'ETCO' :
  399. // 4.5 ETCO Event timing codes
  400. // Time stamp format $xx
  401. // Where time stamp format is:
  402. // $01 (32-bit value) MPEG frames from beginning of file
  403. // $02 (32-bit value) milliseconds from beginning of file
  404. // Followed by a list of key events in the following format:
  405. // Type of event $xx
  406. // Time stamp $xx (xx ...)
  407. // The 'Time stamp' is set to zero if directly at the beginning of the sound
  408. // or after the previous event. All events MUST be sorted in chronological order.
  409. if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1))
  410. {
  411. $this->errors[] = 'Invalid Time Stamp Format byte in ' . $frame_name . ' (' . $source_data_array['timestampformat'] . ')';
  412. }
  413. else
  414. {
  415. $framedata .= chr($source_data_array['timestampformat']);
  416. foreach ($source_data_array as $key => $val)
  417. {
  418. if (! $this->ID3v2IsValidETCOevent($val['typeid']))
  419. {
  420. $this->errors[] = 'Invalid Event Type byte in ' . $frame_name . ' (' . $val['typeid'] . ')';
  421. }
  422. elseif (($key != 'timestampformat') && ($key != 'flags'))
  423. {
  424. if (($val['timestamp'] > 0) && ($previousETCOtimestamp >= $val['timestamp']))
  425. {
  426. // The 'Time stamp' is set to zero if directly at the beginning of the sound
  427. // or after the previous event. All events MUST be sorted in chronological order.
  428. $this->errors[] = 'Out-of-order timestamp in ' . $frame_name . ' (' . $val['timestamp'] . ') for Event Type (' . $val['typeid'] . ')';
  429. }
  430. else
  431. {
  432. $framedata .= chr($val['typeid']);
  433. $framedata .= getid3_lib :: BigEndian2String($val['timestamp'], 4, false);
  434. }
  435. }
  436. }
  437. }
  438. break;
  439. case 'MLLT' :
  440. // 4.6 MLLT MPEG location lookup table
  441. // MPEG frames between reference $xx xx
  442. // Bytes between reference $xx xx xx
  443. // Milliseconds between reference $xx xx xx
  444. // Bits for bytes deviation $xx
  445. // Bits for milliseconds dev. $xx
  446. // Then for every reference the following data is included;
  447. // Deviation in bytes %xxx....
  448. // Deviation in milliseconds %xxx....
  449. if (($source_data_array['framesbetweenreferences'] > 0) && ($source_data_array['framesbetweenreferences'] <= 65535))
  450. {
  451. $framedata .= getid3_lib :: BigEndian2String($source_data_array['framesbetweenreferences'], 2, false);
  452. }
  453. else
  454. {
  455. $this->errors[] = 'Invalid MPEG Frames Between References in ' . $frame_name . ' (' . $source_data_array['framesbetweenreferences'] . ')';
  456. }
  457. if (($source_data_array['bytesbetweenreferences'] > 0) && ($source_data_array['bytesbetweenreferences'] <= 16777215))
  458. {
  459. $framedata .= getid3_lib :: BigEndian2String($source_data_array['bytesbetweenreferences'], 3, false);
  460. }
  461. else
  462. {
  463. $this->errors[] = 'Invalid bytes Between References in ' . $frame_name . ' (' . $source_data_array['bytesbetweenreferences'] . ')';
  464. }
  465. if (($source_data_array['msbetweenreferences'] > 0) && ($source_data_array['msbetweenreferences'] <= 16777215))
  466. {
  467. $framedata .= getid3_lib :: BigEndian2String($source_data_array['msbetweenreferences'], 3, false);
  468. }
  469. else
  470. {
  471. $this->errors[] = 'Invalid Milliseconds Between References in ' . $frame_name . ' (' . $source_data_array['msbetweenreferences'] . ')';
  472. }
  473. if (! $this->IsWithinBitRange($source_data_array['bitsforbytesdeviation'], 8, false))
  474. {
  475. if (($source_data_array['bitsforbytesdeviation'] % 4) == 0)
  476. {
  477. $framedata .= chr($source_data_array['bitsforbytesdeviation']);
  478. }
  479. else
  480. {
  481. $this->errors[] = 'Bits For Bytes Deviation in ' . $frame_name . ' (' . $source_data_array['bitsforbytesdeviation'] . ') must be a multiple of 4.';
  482. }
  483. }
  484. else
  485. {
  486. $this->errors[] = 'Invalid Bits For Bytes Deviation in ' . $frame_name . ' (' . $source_data_array['bitsforbytesdeviation'] . ')';
  487. }
  488. if (! $this->IsWithinBitRange($source_data_array['bitsformsdeviation'], 8, false))
  489. {
  490. if (($source_data_array['bitsformsdeviation'] % 4) == 0)
  491. {
  492. $framedata .= chr($source_data_array['bitsformsdeviation']);
  493. }
  494. else
  495. {
  496. $this->errors[] = 'Bits For Milliseconds Deviation in ' . $frame_name . ' (' . $source_data_array['bitsforbytesdeviation'] . ') must be a multiple of 4.';
  497. }
  498. }
  499. else
  500. {
  501. $this->errors[] = 'Invalid Bits For Milliseconds Deviation in ' . $frame_name . ' (' . $source_data_array['bitsformsdeviation'] . ')';
  502. }
  503. foreach ($source_data_array as $key => $val)
  504. {
  505. if (($key != 'framesbetweenreferences') && ($key != 'bytesbetweenreferences') && ($key != 'msbetweenreferences') && ($key != 'bitsforbytesdeviation') && ($key != 'bitsformsdeviation') && ($key != 'flags'))
  506. {
  507. $unwrittenbitstream .= str_pad(getid3_lib :: Dec2Bin($val['bytedeviation']), $source_data_array['bitsforbytesdeviation'], '0', STR_PAD_LEFT);
  508. $unwrittenbitstream .= str_pad(getid3_lib :: Dec2Bin($val['msdeviation']), $source_data_array['bitsformsdeviation'], '0', STR_PAD_LEFT);
  509. }
  510. }
  511. for($i = 0; $i < strlen($unwrittenbitstream); $i += 8)
  512. {
  513. $highnibble = bindec(substr($unwrittenbitstream, $i, 4)) << 4;
  514. $lownibble = bindec(substr($unwrittenbitstream, $i + 4, 4));
  515. $framedata .= chr($highnibble & $lownibble);
  516. }
  517. break;
  518. case 'SYTC' :
  519. // 4.7 SYTC Synchronised tempo codes
  520. // Time stamp format $xx
  521. // Tempo data <binary data>
  522. // Where time stamp format is:
  523. // $01 (32-bit value) MPEG frames from beginning of file
  524. // $02 (32-bit value) milliseconds from beginning of file
  525. if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1))
  526. {
  527. $this->errors[] = 'Invalid Time Stamp Format byte in ' . $frame_name . ' (' . $source_data_array['timestampformat'] . ')';
  528. }
  529. else
  530. {
  531. $framedata .= chr($source_data_array['timestampformat']);
  532. foreach ($source_data_array as $key => $val)
  533. {
  534. if (! $this->ID3v2IsValidETCOevent($val['typeid']))
  535. {
  536. $this->errors[] = 'Invalid Event Type byte in ' . $frame_name . ' (' . $val['typeid'] . ')';
  537. }
  538. elseif (($key != 'timestampformat') && ($key != 'flags'))
  539. {
  540. if (($val['tempo'] < 0) || ($val['tempo'] > 510))
  541. {
  542. $this->errors[] = 'Invalid Tempo (max = 510) in ' . $frame_name . ' (' . $val['tempo'] . ') at timestamp (' . $val['timestamp'] . ')';
  543. }
  544. else
  545. {
  546. if ($val['tempo'] > 255)
  547. {
  548. $framedata .= chr(255);
  549. $val['tempo'] -= 255;
  550. }
  551. $framedata .= chr($val['tempo']);
  552. $framedata .= getid3_lib :: BigEndian2String($val['timestamp'], 4, false);
  553. }
  554. }
  555. }
  556. }
  557. break;
  558. case 'USLT' :
  559. // 4.8 USLT Unsynchronised lyric/text transcription
  560. // Text encoding $xx
  561. // Language $xx xx xx
  562. // Content descriptor <text string according to encoding> $00 (00)
  563. // Lyrics/text <full text string according to encoding>
  564. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  565. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  566. {
  567. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  568. }
  569. elseif (getid3_id3v2 :: LanguageLookup($source_data_array['language'], true) == '')
  570. {
  571. $this->errors[] = 'Invalid Language in ' . $frame_name . ' (' . $source_data_array['language'] . ')';
  572. }
  573. else
  574. {
  575. $framedata .= chr($source_data_array['encodingid']);
  576. $framedata .= strtolower($source_data_array['language']);
  577. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  578. $framedata .= $source_data_array['data'];
  579. }
  580. break;
  581. case 'SYLT' :
  582. // 4.9 SYLT Synchronised lyric/text
  583. // Text encoding $xx
  584. // Language $xx xx xx
  585. // Time stamp format $xx
  586. // $01 (32-bit value) MPEG frames from beginning of file
  587. // $02 (32-bit value) milliseconds from beginning of file
  588. // Content type $xx
  589. // Content descriptor <text string according to encoding> $00 (00)
  590. // Terminated text to be synced (typically a syllable)
  591. // Sync identifier (terminator to above string) $00 (00)
  592. // Time stamp $xx (xx ...)
  593. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  594. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  595. {
  596. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  597. }
  598. elseif (getid3_id3v2 :: LanguageLookup($source_data_array['language'], true) == '')
  599. {
  600. $this->errors[] = 'Invalid Language in ' . $frame_name . ' (' . $source_data_array['language'] . ')';
  601. }
  602. elseif (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1))
  603. {
  604. $this->errors[] = 'Invalid Time Stamp Format byte in ' . $frame_name . ' (' . $source_data_array['timestampformat'] . ')';
  605. }
  606. elseif (! $this->ID3v2IsValidSYLTtype($source_data_array['contenttypeid']))
  607. {
  608. $this->errors[] = 'Invalid Content Type byte in ' . $frame_name . ' (' . $source_data_array['contenttypeid'] . ')';
  609. }
  610. elseif (! is_array($source_data_array['data']))
  611. {
  612. $this->errors[] = 'Invalid Lyric/Timestamp data in ' . $frame_name . ' (must be an array)';
  613. }
  614. else
  615. {
  616. $framedata .= chr($source_data_array['encodingid']);
  617. $framedata .= strtolower($source_data_array['language']);
  618. $framedata .= chr($source_data_array['timestampformat']);
  619. $framedata .= chr($source_data_array['contenttypeid']);
  620. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  621. ksort($source_data_array['data']);
  622. foreach ($source_data_array['data'] as $key => $val)
  623. {
  624. $framedata .= $val['data'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  625. $framedata .= getid3_lib :: BigEndian2String($val['timestamp'], 4, false);
  626. }
  627. }
  628. break;
  629. case 'COMM' :
  630. // 4.10 COMM Comments
  631. // Text encoding $xx
  632. // Language $xx xx xx
  633. // Short content descrip. <text string according to encoding> $00 (00)
  634. // The actual text <full text string according to encoding>
  635. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  636. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  637. {
  638. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  639. }
  640. elseif (getid3_id3v2 :: LanguageLookup($source_data_array['language'], true) == '')
  641. {
  642. $this->errors[] = 'Invalid Language in ' . $frame_name . ' (' . $source_data_array['language'] . ')';
  643. }
  644. else
  645. {
  646. $framedata .= chr($source_data_array['encodingid']);
  647. $framedata .= strtolower($source_data_array['language']);
  648. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  649. $framedata .= $source_data_array['data'];
  650. }
  651. break;
  652. case 'RVA2' :
  653. // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
  654. // Identification <text string> $00
  655. // The 'identification' string is used to identify the situation and/or
  656. // device where this adjustment should apply. The following is then
  657. // repeated for every channel:
  658. // Type of channel $xx
  659. // Volume adjustment $xx xx
  660. // Bits representing peak $xx
  661. // Peak volume $xx (xx ...)
  662. $framedata .= str_replace("\x00", '', $source_data_array['description']) . "\x00";
  663. foreach ($source_data_array as $key => $val)
  664. {
  665. if ($key != 'description')
  666. {
  667. $framedata .= chr($val['channeltypeid']);
  668. $framedata .= getid3_lib :: BigEndian2String($val['volumeadjust'], 2, false, true); // signed 16-bit
  669. if (! $this->IsWithinBitRange($source_data_array['bitspeakvolume'], 8, false))
  670. {
  671. $framedata .= chr($val['bitspeakvolume']);
  672. if ($val['bitspeakvolume'] > 0)
  673. {
  674. $framedata .= getid3_lib :: BigEndian2String($val['peakvolume'], ceil($val['bitspeakvolume'] / 8), false, false);
  675. }
  676. }
  677. else
  678. {
  679. $this->errors[] = 'Invalid Bits Representing Peak Volume in ' . $frame_name . ' (' . $val['bitspeakvolume'] . ') (range = 0 to 255)';
  680. }
  681. }
  682. }
  683. break;
  684. case 'RVAD' :
  685. // 4.12 RVAD Relative volume adjustment (ID3v2.3 only)
  686. // Increment/decrement %00fedcba
  687. // Bits used for volume descr. $xx
  688. // Relative volume change, right $xx xx (xx ...) // a
  689. // Relative volume change, left $xx xx (xx ...) // b
  690. // Peak volume right $xx xx (xx ...)
  691. // Peak volume left $xx xx (xx ...)
  692. // Relative volume change, right back $xx xx (xx ...) // c
  693. // Relative volume change, left back $xx xx (xx ...) // d
  694. // Peak volume right back $xx xx (xx ...)
  695. // Peak volume left back $xx xx (xx ...)
  696. // Relative volume change, center $xx xx (xx ...) // e
  697. // Peak volume center $xx xx (xx ...)
  698. // Relative volume change, bass $xx xx (xx ...) // f
  699. // Peak volume bass $xx xx (xx ...)
  700. if (! $this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false))
  701. {
  702. $this->errors[] = 'Invalid Bits For Volume Description byte in ' . $frame_name . ' (' . $source_data_array['bitsvolume'] . ') (range = 1 to 255)';
  703. }
  704. else
  705. {
  706. $incdecflag .= '00';
  707. $incdecflag .= $source_data_array['incdec']['right'] ? '1' : '0'; // a - Relative volume change, right
  708. $incdecflag .= $source_data_array['incdec']['left'] ? '1' : '0'; // b - Relative volume change, left
  709. $incdecflag .= $source_data_array['incdec']['rightrear'] ? '1' : '0'; // c - Relative volume change, right back
  710. $incdecflag .= $source_data_array['incdec']['leftrear'] ? '1' : '0'; // d - Relative volume change, left back
  711. $incdecflag .= $source_data_array['incdec']['center'] ? '1' : '0'; // e - Relative volume change, center
  712. $incdecflag .= $source_data_array['incdec']['bass'] ? '1' : '0'; // f - Relative volume change, bass
  713. $framedata .= chr(bindec($incdecflag));
  714. $framedata .= chr($source_data_array['bitsvolume']);
  715. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['right'], ceil($source_data_array['bitsvolume'] / 8), false);
  716. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['left'], ceil($source_data_array['bitsvolume'] / 8), false);
  717. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['right'], ceil($source_data_array['bitsvolume'] / 8), false);
  718. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['left'], ceil($source_data_array['bitsvolume'] / 8), false);
  719. 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'])
  720. {
  721. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['rightrear'], ceil($source_data_array['bitsvolume'] / 8), false);
  722. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['leftrear'], ceil($source_data_array['bitsvolume'] / 8), false);
  723. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['rightrear'], ceil($source_data_array['bitsvolume'] / 8), false);
  724. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['leftrear'], ceil($source_data_array['bitsvolume'] / 8), false);
  725. }
  726. if ($source_data_array['volumechange']['center'] || $source_data_array['peakvolume']['center'] || $source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass'])
  727. {
  728. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['center'], ceil($source_data_array['bitsvolume'] / 8), false);
  729. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['center'], ceil($source_data_array['bitsvolume'] / 8), false);
  730. }
  731. if ($source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass'])
  732. {
  733. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['bass'], ceil($source_data_array['bitsvolume'] / 8), false);
  734. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['bass'], ceil($source_data_array['bitsvolume'] / 8), false);
  735. }
  736. }
  737. break;
  738. case 'EQU2' :
  739. // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only)
  740. // Interpolation method $xx
  741. // $00 Band
  742. // $01 Linear
  743. // Identification <text string> $00
  744. // The following is then repeated for every adjustment point
  745. // Frequency $xx xx
  746. // Volume adjustment $xx xx
  747. if (($source_data_array['interpolationmethod'] < 0) || ($source_data_array['interpolationmethod'] > 1))
  748. {
  749. $this->errors[] = 'Invalid Interpolation Method byte in ' . $frame_name . ' (' . $source_data_array['interpolationmethod'] . ') (valid = 0 or 1)';
  750. }
  751. else
  752. {
  753. $framedata .= chr($source_data_array['interpolationmethod']);
  754. $framedata .= str_replace("\x00", '', $source_data_array['description']) . "\x00";
  755. foreach ($source_data_array['data'] as $key => $val)
  756. {
  757. $framedata .= getid3_lib :: BigEndian2String(intval(round($key * 2)), 2, false);
  758. $framedata .= getid3_lib :: BigEndian2String($val, 2, false, true); // signed 16-bit
  759. }
  760. }
  761. break;
  762. case 'EQUA' :
  763. // 4.12 EQUA Equalisation (ID3v2.3 only)
  764. // Adjustment bits $xx
  765. // This is followed by 2 bytes + ('adjustment bits' rounded up to the
  766. // nearest byte) for every equalisation band in the following format,
  767. // giving a frequency range of 0 - 32767Hz:
  768. // Increment/decrement %x (MSB of the Frequency)
  769. // Frequency (lower 15 bits)
  770. // Adjustment $xx (xx ...)
  771. if (! $this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false))
  772. {
  773. $this->errors[] = 'Invalid Adjustment Bits byte in ' . $frame_name . ' (' . $source_data_array['bitsvolume'] . ') (range = 1 to 255)';
  774. }
  775. else
  776. {
  777. $framedata .= chr($source_data_array['adjustmentbits']);
  778. foreach ($source_data_array as $key => $val)
  779. {
  780. if ($key != 'bitsvolume')
  781. {
  782. if (($key > 32767) || ($key < 0))
  783. {
  784. $this->errors[] = 'Invalid Frequency in ' . $frame_name . ' (' . $key . ') (range = 0 to 32767)';
  785. }
  786. else
  787. {
  788. if ($val >= 0)
  789. {
  790. // put MSB of frequency to 1 if increment, 0 if decrement
  791. $key |= 0x8000;
  792. }
  793. $framedata .= getid3_lib :: BigEndian2String($key, 2, false);
  794. $framedata .= getid3_lib :: BigEndian2String($val, ceil($source_data_array['adjustmentbits'] / 8), false);
  795. }
  796. }
  797. }
  798. }
  799. break;
  800. case 'RVRB' :
  801. // 4.13 RVRB Reverb
  802. // Reverb left (ms) $xx xx
  803. // Reverb right (ms) $xx xx
  804. // Reverb bounces, left $xx
  805. // Reverb bounces, right $xx
  806. // Reverb feedback, left to left $xx
  807. // Reverb feedback, left to right $xx
  808. // Reverb feedback, right to right $xx
  809. // Reverb feedback, right to left $xx
  810. // Premix left to right $xx
  811. // Premix right to left $xx
  812. if (! $this->IsWithinBitRange($source_data_array['left'], 16, false))
  813. {
  814. $this->errors[] = 'Invalid Reverb Left in ' . $frame_name . ' (' . $source_data_array['left'] . ') (range = 0 to 65535)';
  815. }
  816. elseif (! $this->IsWithinBitRange($source_data_array['right'], 16, false))
  817. {
  818. $this->errors[] = 'Invalid Reverb Left in ' . $frame_name . ' (' . $source_data_array['right'] . ') (range = 0 to 65535)';
  819. }
  820. elseif (! $this->IsWithinBitRange($source_data_array['bouncesL'], 8, false))
  821. {
  822. $this->errors[] = 'Invalid Reverb Bounces, Left in ' . $frame_name . ' (' . $source_data_array['bouncesL'] . ') (range = 0 to 255)';
  823. }
  824. elseif (! $this->IsWithinBitRange($source_data_array['bouncesR'], 8, false))
  825. {
  826. $this->errors[] = 'Invalid Reverb Bounces, Right in ' . $frame_name . ' (' . $source_data_array['bouncesR'] . ') (range = 0 to 255)';
  827. }
  828. elseif (! $this->IsWithinBitRange($source_data_array['feedbackLL'], 8, false))
  829. {
  830. $this->errors[] = 'Invalid Reverb Feedback, Left-To-Left in ' . $frame_name . ' (' . $source_data_array['feedbackLL'] . ') (range = 0 to 255)';
  831. }
  832. elseif (! $this->IsWithinBitRange($source_data_array['feedbackLR'], 8, false))
  833. {
  834. $this->errors[] = 'Invalid Reverb Feedback, Left-To-Right in ' . $frame_name . ' (' . $source_data_array['feedbackLR'] . ') (range = 0 to 255)';
  835. }
  836. elseif (! $this->IsWithinBitRange($source_data_array['feedbackRR'], 8, false))
  837. {
  838. $this->errors[] = 'Invalid Reverb Feedback, Right-To-Right in ' . $frame_name . ' (' . $source_data_array['feedbackRR'] . ') (range = 0 to 255)';
  839. }
  840. elseif (! $this->IsWithinBitRange($source_data_array['feedbackRL'], 8, false))
  841. {
  842. $this->errors[] = 'Invalid Reverb Feedback, Right-To-Left in ' . $frame_name . ' (' . $source_data_array['feedbackRL'] . ') (range = 0 to 255)';
  843. }
  844. elseif (! $this->IsWithinBitRange($source_data_array['premixLR'], 8, false))
  845. {
  846. $this->errors[] = 'Invalid Premix, Left-To-Right in ' . $frame_name . ' (' . $source_data_array['premixLR'] . ') (range = 0 to 255)';
  847. }
  848. elseif (! $this->IsWithinBitRange($source_data_array['premixRL'], 8, false))
  849. {
  850. $this->errors[] = 'Invalid Premix, Right-To-Left in ' . $frame_name . ' (' . $source_data_array['premixRL'] . ') (range = 0 to 255)';
  851. }
  852. else
  853. {
  854. $framedata .= getid3_lib :: BigEndian2String($source_data_array['left'], 2, false);
  855. $framedata .= getid3_lib :: BigEndian2String($source_data_array['right'], 2, false);
  856. $framedata .= chr($source_data_array['bouncesL']);
  857. $framedata .= chr($source_data_array['bouncesR']);
  858. $framedata .= chr($source_data_array['feedbackLL']);
  859. $framedata .= chr($source_data_array['feedbackLR']);
  860. $framedata .= chr($source_data_array['feedbackRR']);
  861. $framedata .= chr($source_data_array['feedbackRL']);
  862. $framedata .= chr($source_data_array['premixLR']);
  863. $framedata .= chr($source_data_array['premixRL']);
  864. }
  865. break;
  866. case 'APIC' :
  867. // 4.14 APIC Attached picture
  868. // Text encoding $xx
  869. // MIME type <text string> $00
  870. // Picture type $xx
  871. // Description <text string according to encoding> $00 (00)
  872. // Picture data <binary data>
  873. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  874. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  875. {
  876. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  877. }
  878. elseif (! $this->ID3v2IsValidAPICpicturetype($source_data_array['picturetypeid']))
  879. {
  880. $this->errors[] = 'Invalid Picture Type byte in ' . $frame_name . ' (' . $source_data_array['picturetypeid'] . ') for ID3v2.' . $this->majorversion;
  881. }
  882. elseif (($this->majorversion >= 3) && (! $this->ID3v2IsValidAPICimageformat($source_data_array['mime'])))
  883. {
  884. $this->errors[] = 'Invalid MIME Type in ' . $frame_name . ' (' . $source_data_array['mime'] . ') for ID3v2.' . $this->majorversion;
  885. }
  886. elseif (($source_data_array['mime'] == '-->') && (! $this->IsValidURL($source_data_array['data'], false, false)))
  887. {
  888. //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
  889. // probably should be an error, need to rewrite IsValidURL() to handle other encodings
  890. $this->warnings[] = 'Invalid URL in ' . $frame_name . ' (' . $source_data_array['data'] . ')';
  891. }
  892. else
  893. {
  894. $framedata .= chr($source_data_array['encodingid']);
  895. $framedata .= str_replace("\x00", '', $source_data_array['mime']) . "\x00";
  896. $framedata .= chr($source_data_array['picturetypeid']);
  897. $framedata .= @$source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  898. $framedata .= $source_data_array['data'];
  899. }
  900. break;
  901. case 'GEOB' :
  902. // 4.15 GEOB General encapsulated object
  903. // Text encoding $xx
  904. // MIME type <text string> $00
  905. // Filename <text string according to encoding> $00 (00)
  906. // Content description <text string according to encoding> $00 (00)
  907. // Encapsulated object <binary data>
  908. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  909. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  910. {
  911. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  912. }
  913. elseif (! $this->IsValidMIMEstring($source_data_array['mime']))
  914. {
  915. $this->errors[] = 'Invalid MIME Type in ' . $frame_name . ' (' . $source_data_array['mime'] . ')';
  916. }
  917. elseif (! $source_data_array['description'])
  918. {
  919. $this->errors[] = 'Missing Description in ' . $frame_name;
  920. }
  921. else
  922. {
  923. $framedata .= chr($source_data_array['encodingid']);
  924. $framedata .= str_replace("\x00", '', $source_data_array['mime']) . "\x00";
  925. $framedata .= $source_data_array['filename'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  926. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  927. $framedata .= $source_data_array['data'];
  928. }
  929. break;
  930. case 'PCNT' :
  931. // 4.16 PCNT Play counter
  932. // When the counter reaches all one's, one byte is inserted in
  933. // front of the counter thus making the counter eight bits bigger
  934. // Counter $xx xx xx xx (xx ...)
  935. $framedata .= getid3_lib :: BigEndian2String($source_data_array['data'], 4, false);
  936. break;
  937. case 'POPM' :
  938. // 4.17 POPM Popularimeter
  939. // When the counter reaches all one's, one byte is inserted in
  940. // front of the counter thus making the counter eight bits bigger
  941. // Email to user <text string> $00
  942. // Rating $xx
  943. // Counter $xx xx xx xx (xx ...)
  944. if (! $this->IsWithinBitRange($source_data_array['rating'], 8, false))
  945. {
  946. $this->errors[] = 'Invalid Rating byte in ' . $frame_name . ' (' . $source_data_array['rating'] . ') (range = 0 to 255)';
  947. }
  948. elseif (! IsValidEmail($source_data_array['email']))
  949. {
  950. $this->errors[] = 'Invalid Email in ' . $frame_name . ' (' . $source_data_array['email'] . ')';
  951. }
  952. else
  953. {
  954. $framedata .= str_replace("\x00", '', $source_data_array['email']) . "\x00";
  955. $framedata .= chr($source_data_array['rating']);
  956. $framedata .= getid3_lib :: BigEndian2String($source_data_array['data'], 4, false);
  957. }
  958. break;
  959. case 'RBUF' :
  960. // 4.18 RBUF Recommended buffer size
  961. // Buffer size $xx xx xx
  962. // Embedded info flag %0000000x
  963. // Offset to next tag $xx xx xx xx
  964. if (! $this->IsWithinBitRange($source_data_array['buffersize'], 24, false))
  965. {
  966. $this->errors[] = 'Invalid Buffer Size in ' . $frame_name;
  967. }
  968. elseif (! $this->IsWithinBitRange($source_data_array['nexttagoffset'], 32, false))
  969. {
  970. $this->errors[] = 'Invalid Offset To Next Tag in ' . $frame_name;
  971. }
  972. else
  973. {
  974. $framedata .= getid3_lib :: BigEndian2String($source_data_array['buffersize'], 3, false);
  975. $flag .= '0000000';
  976. $flag .= $source_data_array['flags']['embededinfo'] ? '1' : '0';
  977. $framedata .= chr(bindec($flag));
  978. $framedata .= getid3_lib :: BigEndian2String($source_data_array['nexttagoffset'], 4, false);
  979. }
  980. break;
  981. case 'AENC' :
  982. // 4.19 AENC Audio encryption
  983. // Owner identifier <text string> $00
  984. // Preview start $xx xx
  985. // Preview length $xx xx
  986. // Encryption info <binary data>
  987. if (! $this->IsWithinBitRange($source_data_array['previewstart'], 16, false))
  988. {
  989. $this->errors[] = 'Invalid Preview Start in ' . $frame_name . ' (' . $source_data_array['previewstart'] . ')';
  990. }
  991. elseif (! $this->IsWithinBitRange($source_data_array['previewlength'], 16, false))
  992. {
  993. $this->errors[] = 'Invalid Preview Length in ' . $frame_name . ' (' . $source_data_array['previewlength'] . ')';
  994. }
  995. else
  996. {
  997. $framedata .= str_replace("\x00", '', $source_data_array['ownerid']) . "\x00";
  998. $framedata .= getid3_lib :: BigEndian2String($source_data_array['previewstart'], 2, false);
  999. $framedata .= getid3_lib :: BigEndian2String($source_data_array['previewlength'], 2, false);
  1000. $framedata .= $source_data_array['encryptioninfo'];
  1001. }
  1002. break;
  1003. case 'LINK' :
  1004. // 4.20 LINK Linked information
  1005. // Frame identifier $xx xx xx xx
  1006. // URL <text string> $00
  1007. // ID and additional data <text string(s)>
  1008. if (! getid3_id3v2 :: IsValidID3v2FrameName($source_data_array['frameid'], $this->majorversion))
  1009. {
  1010. $this->errors[] = 'Invalid Frame Identifier in ' . $frame_name . ' (' . $source_data_array['frameid'] . ')';
  1011. }
  1012. elseif (! $this->IsValidURL($source_data_array['data'], true, false))
  1013. {
  1014. //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
  1015. // probably should be an error, need to rewrite IsValidURL() to handle other encodings
  1016. $this->warnings[] = 'Invalid URL in ' . $frame_name . ' (' . $source_data_array['data'] . ')';
  1017. }
  1018. 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'] == ''))
  1019. {
  1020. $this->errors[] = 'Content Descriptor must be specified as additional data for Frame Identifier of ' . $source_data_array['frameid'] . ' in ' . $frame_name;
  1021. }
  1022. elseif (($source_data_array['frameid'] == 'USER') && (getid3_id3v2 :: LanguageLookup($source_data_array['additionaldata'], true) == ''))
  1023. {
  1024. $this->errors[] = 'Language must be specified as additional data for Frame Identifier of ' . $source_data_array['frameid'] . ' in ' . $frame_name;
  1025. }
  1026. elseif (($source_data_array['frameid'] == 'PRIV') && ($source_data_array['additionaldata'] == ''))
  1027. {
  1028. $this->errors[] = 'Owner Identifier must be specified as additional data for Frame Identifier of ' . $source_data_array['frameid'] . ' in ' . $frame_name;
  1029. }
  1030. 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) == '')))
  1031. {
  1032. $this->errors[] = 'Language followed by Content Descriptor must be specified as additional data for Frame Identifier of ' . $source_data_array['frameid'] . ' in ' . $frame_name;
  1033. }
  1034. else
  1035. {
  1036. $framedata .= $source_data_array['frameid'];
  1037. $framedata .= str_replace("\x00", '', $source_data_array['data']) . "\x00";
  1038. switch ($source_data_array['frameid'])
  1039. {
  1040. case 'COMM' :
  1041. case 'SYLT' :
  1042. case 'USLT' :
  1043. case 'PRIV' :
  1044. case 'USER' :
  1045. case 'AENC' :
  1046. case 'APIC' :
  1047. case 'GEOB' :
  1048. case 'TXXX' :
  1049. $framedata .= $source_data_array['additionaldata'];
  1050. break;
  1051. case 'ASPI' :
  1052. case 'ETCO' :
  1053. case 'EQU2' :
  1054. case 'MCID' :
  1055. case 'MLLT' :
  1056. case 'OWNE' :
  1057. case 'RVA2' :
  1058. case 'RVRB' :
  1059. case 'SYTC' :
  1060. case 'IPLS' :
  1061. case 'RVAD' :
  1062. case 'EQUA' :
  1063. // no additional data required
  1064. break;
  1065. case 'RBUF' :
  1066. if ($this->majorversion == 3)
  1067. {
  1068. // no additional data required
  1069. }
  1070. else
  1071. {
  1072. $this->errors[] = $source_data_array['frameid'] . ' is not a valid Frame Identifier in ' . $frame_name . ' (in ID3v2.' . $this->majorversion . ')';
  1073. }
  1074. default :
  1075. if ((substr($source_data_array['frameid'], 0, 1) == 'T') || (substr($source_data_array['frameid'], 0, 1) == 'W'))
  1076. {
  1077. // no additional data required
  1078. }
  1079. else
  1080. {
  1081. $this->errors[] = $source_data_array['frameid'] . ' is not a valid Frame Identifier in ' . $frame_name . ' (in ID3v2.' . $this->majorversion . ')';
  1082. }
  1083. break;
  1084. }
  1085. }
  1086. break;
  1087. case 'POSS' :
  1088. // 4.21 POSS Position synchronisation frame (ID3v2.3+ only)
  1089. // Time stamp format $xx
  1090. // Position $xx (xx ...)
  1091. if (($source_data_array['timestampformat'] < 1) || ($source_data_array['timestampformat'] > 2))
  1092. {
  1093. $this->errors[] = 'Invalid Time Stamp Format in ' . $frame_name . ' (' . $source_data_array['timestampformat'] . ') (valid = 1 or 2)';
  1094. }
  1095. elseif (! $this->IsWithinBitRange($source_data_array['position'], 32, false))
  1096. {
  1097. $this->errors[] = 'Invalid Position in ' . $frame_name . ' (' . $source_data_array['position'] . ') (range = 0 to 4294967295)';
  1098. }
  1099. else
  1100. {
  1101. $framedata .= chr($source_data_array['timestampformat']);
  1102. $framedata .= getid3_lib :: BigEndian2String($source_data_array['position'], 4, false);
  1103. }
  1104. break;
  1105. case 'USER' :
  1106. // 4.22 USER Terms of use (ID3v2.3+ only)
  1107. // Text encoding $xx
  1108. // Language $xx xx xx
  1109. // The actual text <text string according to encoding>
  1110. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  1111. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  1112. {
  1113. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ')';
  1114. }
  1115. elseif (getid3_id3v2 :: LanguageLookup($source_data_array['language'], true) == '')
  1116. {
  1117. $this->errors[] = 'Invalid Language in ' . $frame_name . ' (' . $source_data_array['language'] . ')';
  1118. }
  1119. else
  1120. {
  1121. $framedata .= chr($source_data_array['encodingid']);
  1122. $framedata .= strtolower($source_data_array['language']);
  1123. $framedata .= $source_data_array['data'];
  1124. }
  1125. break;
  1126. case 'OWNE' :
  1127. // 4.23 OWNE Ownership frame (ID3v2.3+ only)
  1128. // Text encoding $xx
  1129. // Price paid <text string> $00
  1130. // Date of purch. <text string>
  1131. // Seller <text string according to encoding>
  1132. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  1133. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  1134. {
  1135. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ')';
  1136. }
  1137. elseif (! $this->IsANumber($source_data_array['pricepaid']['value'], false))
  1138. {
  1139. $this->errors[] = 'Invalid Price Paid in ' . $frame_name . ' (' . $source_data_array['pricepaid']['value'] . ')';
  1140. }
  1141. elseif (! $this->IsValidDateStampString($source_data_array['purchasedate']))
  1142. {
  1143. $this->errors[] = 'Invalid Date Of Purchase in ' . $frame_name . ' (' . $source_data_array['purchasedate'] . ') (format = YYYYMMDD)';
  1144. }
  1145. else
  1146. {
  1147. $framedata .= chr($source_data_array['encodingid']);
  1148. $framedata .= str_replace("\x00", '', $source_data_array['pricepaid']['value']) . "\x00";
  1149. $framedata .= $source_data_array['purchasedate'];
  1150. $framedata .= $source_data_array['seller'];
  1151. }
  1152. break;
  1153. case 'COMR' :
  1154. // 4.24 COMR Commercial frame (ID3v2.3+ only)
  1155. // Text encoding $xx
  1156. // Price string <text string> $00
  1157. // Valid until <text string>
  1158. // Contact URL <text string> $00
  1159. // Received as $xx
  1160. // Name of seller <text string according to encoding> $00 (00)
  1161. // Description <text string according to encoding> $00 (00)
  1162. // Picture MIME type <string> $00
  1163. // Seller logo <binary data>
  1164. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  1165. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  1166. {
  1167. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ')';
  1168. }
  1169. elseif (! $this->IsValidDateStampString($source_data_array['pricevaliduntil']))
  1170. {
  1171. $this->errors[] = 'Invalid Valid Until date in ' . $frame_name . ' (' . $source_data_array['pricevaliduntil'] . ') (format = YYYYMMDD)';
  1172. }
  1173. elseif (! $this->IsValidURL($source_data_array['contacturl'], false, true))
  1174. {
  1175. $this->errors[] = 'Invalid Contact URL in ' . $frame_name . ' (' . $source_data_array['contacturl'] . ') (allowed schemes: http, https, ftp, mailto)';
  1176. }
  1177. elseif (! $this->ID3v2IsValidCOMRreceivedAs($source_data_array['receivedasid']))
  1178. {
  1179. $this->errors[] = 'Invalid Received As byte in ' . $frame_name . ' (' . $source_data_array['contacturl'] . ') (range = 0 to 8)';
  1180. }
  1181. elseif (! $this->IsValidMIMEstring($source_data_array['mime']))
  1182. {
  1183. $this->errors[] = 'Invalid MIME Type in ' . $frame_name . ' (' . $source_data_array['mime'] . ')';
  1184. }
  1185. else
  1186. {
  1187. $framedata .= chr($source_data_array['encodingid']);
  1188. unset($pricestring);
  1189. foreach ($source_data_array['price'] as $key => $val)
  1190. {
  1191. if ($this->ID3v2IsValidPriceString($key . $val['value']))
  1192. {
  1193. $pricestrings[] = $key . $val['value'];
  1194. }
  1195. else
  1196. {
  1197. $this->errors[] = 'Invalid Price String in ' . $frame_name . ' (' . $key . $val['value'] . ')';
  1198. }
  1199. }
  1200. $framedata .= implode('/', $pricestrings);
  1201. $framedata .= $source_data_array['pricevaliduntil'];
  1202. $framedata .= str_replace("\x00", '', $source_data_array['contacturl']) . "\x00";
  1203. $framedata .= chr($source_data_array['receivedasid']);
  1204. $framedata .= $source_data_array['sellername'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  1205. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  1206. $framedata .= $source_data_array['mime'] . "\x00";
  1207. $framedata .= $source_data_array['logo'];
  1208. }
  1209. break;
  1210. case 'ENCR' :
  1211. // 4.25 ENCR Encryption method registration (ID3v2.3+ only)
  1212. // Owner identifier <text string> $00
  1213. // Method symbol $xx
  1214. // Encryption data <binary data>
  1215. if (! $this->IsWithinBitRange($source_data_array['methodsymbol'], 8, false))
  1216. {
  1217. $this->errors[] = 'Invalid Group Symbol in ' . $frame_name . ' (' . $source_data_array['methodsymbol'] . ') (range = 0 to 255)';
  1218. }
  1219. else
  1220. {
  1221. $framedata .= str_replace("\x00", '', $source_data_array['ownerid']) . "\x00";
  1222. $framedata .= ord($source_data_array['methodsymbol']);
  1223. $framedata .= $source_data_array['data'];
  1224. }
  1225. break;
  1226. case 'GRID' :
  1227. // 4.26 GRID Group identification registration (ID3v2.3+ only)
  1228. // Owner identifier <text string> $00
  1229. // Group symbol $xx
  1230. // Group dependent data <binary data>
  1231. if (! $this->IsWithinBitRange($source_data_array['groupsymbol'], 8, false))
  1232. {
  1233. $this->errors[] = 'Invalid Group Symbol in ' . $frame_name . ' (' . $source_data_array['groupsymbol'] . ') (range = 0 to 255)';
  1234. }
  1235. else
  1236. {
  1237. $framedata .= str_replace("\x00", '', $source_data_array['ownerid']) . "\x00";
  1238. $framedata .= ord($source_data_array['groupsymbol']);
  1239. $framedata .= $source_data_array['data'];
  1240. }
  1241. break;
  1242. case 'PRIV' :
  1243. // 4.27 PRIV Private frame (ID3v2.3+ only)
  1244. // Owner identifier <text string> $00
  1245. // The private data <binary data>
  1246. $framedata .= str_replace("\x00", '', $source_data_array['ownerid']) . "\x00";
  1247. $framedata .= $source_data_array['data'];
  1248. break;
  1249. case 'SIGN' :
  1250. // 4.28 SIGN Signature frame (ID3v2.4+ only)
  1251. // Group symbol $xx
  1252. // Signature <binary data>
  1253. if (! $this->IsWithinBitRange($source_data_array['groupsymbol'], 8, false))
  1254. {
  1255. $this->errors[] = 'Invalid Group Symbol in ' . $frame_name . ' (' . $source_data_array['groupsymbol'] . ') (range = 0 to 255)';
  1256. }
  1257. else
  1258. {
  1259. $framedata .= ord($source_data_array['groupsymbol']);
  1260. $framedata .= $source_data_array['data'];
  1261. }
  1262. break;
  1263. case 'SEEK' :
  1264. // 4.29 SEEK Seek frame (ID3v2.4+ only)
  1265. // Minimum offset to next tag $xx xx xx xx
  1266. if (! $this->IsWithinBitRange($source_data_array['data'], 32, false))
  1267. {
  1268. $this->errors[] = 'Invalid Minimum Offset in ' . $frame_name . ' (' . $source_data_array['data'] . ') (range = 0 to 4294967295)';
  1269. }
  1270. else
  1271. {
  1272. $framedata .= getid3_lib :: BigEndian2String($source_data_array['data'], 4, false);
  1273. }
  1274. break;
  1275. case 'ASPI' :
  1276. // 4.30 ASPI Audio seek point index (ID3v2.4+ only)
  1277. // Indexed data start (S) $xx xx xx xx
  1278. // Indexed data length (L) $xx xx xx xx
  1279. // Number of index points (N) $xx xx
  1280. // Bits per index point (b) $xx
  1281. // Then for every index point the following data is included:
  1282. // Fraction at index (Fi) $xx (xx)
  1283. if (! $this->IsWithinBitRange($source_data_array['datastart'], 32, false))
  1284. {
  1285. $this->errors[] = 'Invalid Indexed Data Start in ' . $frame_name . ' (' . $source_data_array['datastart'] . ') (range = 0 to 4294967295)';
  1286. }
  1287. elseif (! $this->IsWithinBitRange($source_data_array['datalength'], 32, false))
  1288. {
  1289. $this->errors[] = 'Invalid Indexed Data Length in ' . $frame_name . ' (' . $source_data_array['datalength'] . ') (range = 0 to 4294967295)';
  1290. }
  1291. elseif (! $this->IsWithinBitRange($source_data_array['indexpoints'], 16, false))
  1292. {
  1293. $this->errors[] = 'Invalid Number Of Index Points in ' . $frame_name . ' (' . $source_data_array['indexpoints'] . ') (range = 0 to 65535)';
  1294. }
  1295. elseif (! $this->IsWithinBitRange($source_data_array['bitsperpoint'], 8, false))
  1296. {
  1297. $this->errors[] = 'Invalid Bits Per Index Point in ' . $frame_name . ' (' . $source_data_array['bitsperpoint'] . ') (range = 0 to 255)';
  1298. }
  1299. elseif ($source_data_array['indexpoints'] != count($source_data_array['indexes']))
  1300. {
  1301. $this->errors[] = 'Number Of Index Points does not match actual supplied data in ' . $frame_name;
  1302. }
  1303. else
  1304. {
  1305. $framedata .= getid3_lib :: BigEndian2String($source_data_array['datastart'], 4, false);
  1306. $framedata .= getid3_lib :: BigEndian2String($source_data_array['datalength'], 4, false);
  1307. $framedata .= getid3_lib :: BigEndian2String($source_data_array['indexpoints'], 2, false);
  1308. $framedata .= getid3_lib :: BigEndian2String($source_data_array['bitsperpoint'], 1, false);
  1309. foreach ($source_data_array['indexes'] as $key => $val)
  1310. {
  1311. $framedata .= getid3_lib :: BigEndian2String($val, ceil($source_data_array['bitsperpoint'] / 8), false);
  1312. }
  1313. }
  1314. break;
  1315. case 'RGAD' :
  1316. // RGAD Replay Gain Adjustment
  1317. // http://privatewww.essex.ac.uk/~djmrob/replaygain/
  1318. // Peak Amplitude $xx $xx $xx $xx
  1319. // Radio Replay Gain Adjustment %aaabbbcd %dddddddd
  1320. // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd
  1321. // a - name code
  1322. // b - originator code
  1323. // c - sign bit
  1324. // d - replay gain adjustment
  1325. if (($source_data_array['track_adjustment'] > 51) || ($source_data_array['track_adjustment'] < - 51))
  1326. {
  1327. $this->errors[] = 'Invalid Track Adjustment in ' . $frame_name . ' (' . $source_data_array['track_adjustment'] . ') (range = -51.0 to +51.0)';
  1328. }
  1329. elseif (($source_data_array['album_adjustment'] > 51) || ($source_data_array['album_adjustment'] < - 51))
  1330. {
  1331. $this->errors[] = 'Invalid Album Adjustment in ' . $frame_name . ' (' . $source_data_array['album_adjustment'] . ') (range = -51.0 to +51.0)';
  1332. }
  1333. elseif (! $this->ID3v2IsValidRGADname($source_data_array['raw']['track_name']))
  1334. {
  1335. $this->errors[] = 'Invalid Track Name Code in ' . $frame_name . ' (' . $source_data_array['raw']['track_name'] . ') (range = 0 to 2)';
  1336. }
  1337. elseif (! $this->ID3v2IsValidRGADname($source_data_array['raw']['album_name']))
  1338. {
  1339. $this->errors[] = 'Invalid Album Name Code in ' . $frame_name . ' (' . $source_data_array['raw']['album_name'] . ') (range = 0 to 2)';
  1340. }
  1341. elseif (! $this->ID3v2IsValidRGADoriginator($source_data_array['raw']['track_originator']))
  1342. {
  1343. $this->errors[] = 'Invalid Track Originator Code in ' . $frame_name . ' (' . $source_data_array['raw']['track_originator'] . ') (range = 0 to 3)';
  1344. }
  1345. elseif (! $this->ID3v2IsValidRGADoriginator($source_data_array['raw']['album_originator']))
  1346. {
  1347. $this->errors[] = 'Invalid Album Originator Code in ' . $frame_name . ' (' . $source_data_array['raw']['album_originator'] . ') (range = 0 to 3)';
  1348. }
  1349. else
  1350. {
  1351. $framedata .= getid3_lib :: Float2String($source_data_array['peakamplitude'], 32);
  1352. $framedata .= getid3_lib :: RGADgainString($source_data_array['raw']['track_name'], $source_data_array['raw']['track_originator'], $source_data_array['track_adjustment']);
  1353. $framedata .= getid3_lib :: RGADgainString($source_data_array['raw']['album_name'], $source_data_array['raw']['album_originator'], $source_data_array['album_adjustment']);
  1354. }
  1355. break;
  1356. default :
  1357. if ($frame_name{0} == 'T')
  1358. {
  1359. // 4.2. T??? Text information frames
  1360. // Text encoding $xx
  1361. // Information <text string(s) according to encoding>
  1362. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  1363. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  1364. {
  1365. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  1366. }
  1367. else
  1368. {
  1369. $framedata .= chr($source_data_array['encodingid']);
  1370. $framedata .= $source_data_array['data'];
  1371. }
  1372. }
  1373. elseif ($frame_name{0} == 'W')
  1374. {
  1375. // 4.3. W??? URL link frames
  1376. // URL <text string>
  1377. if (! $this->IsValidURL($source_data_array['data'], false, false))
  1378. {
  1379. //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
  1380. // probably should be an error, need to rewrite IsValidURL() to handle other encodings
  1381. $this->warnings[] = 'Invalid URL in ' . $frame_name . ' (' . $source_data_array['data'] . ')';
  1382. }
  1383. else
  1384. {
  1385. $framedata .= $source_data_array['data'];
  1386. }
  1387. }
  1388. else
  1389. {
  1390. $this->errors[] = $frame_name . ' not yet supported in $this->GenerateID3v2FrameData()';
  1391. }
  1392. break;
  1393. }
  1394. }
  1395. if (! empty($this->errors))
  1396. {
  1397. return false;
  1398. }
  1399. return $framedata;
  1400. }
  1401. function ID3v2FrameIsAllowed($frame_name, $source_data_array)
  1402. {
  1403. static $PreviousFrames = array();
  1404. if ($frame_name === null)
  1405. {
  1406. // if the writing functions are called multiple times, the static array needs to be
  1407. // cleared - this can be done by calling $this->ID3v2FrameIsAllowed(null, '')
  1408. $PreviousFrames = array();
  1409. return true;
  1410. }
  1411. if ($this->majorversion == 4)
  1412. {
  1413. switch ($frame_name)
  1414. {
  1415. case 'UFID' :
  1416. case 'AENC' :
  1417. case 'ENCR' :
  1418. case 'GRID' :
  1419. if (! isset($source_data_array['ownerid']))
  1420. {
  1421. $this->errors[] = '[ownerid] not specified for ' . $frame_name;
  1422. }
  1423. elseif (in_array($frame_name . $source_data_array['ownerid'], $PreviousFrames))
  1424. {
  1425. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same OwnerID (' . $source_data_array['ownerid'] . ')';
  1426. }
  1427. else
  1428. {
  1429. $PreviousFrames[] = $frame_name . $source_data_array['ownerid'];
  1430. }
  1431. break;
  1432. case 'TXXX' :
  1433. case 'WXXX' :
  1434. case 'RVA2' :
  1435. case 'EQU2' :
  1436. case 'APIC' :
  1437. case 'GEOB' :
  1438. if (! isset($source_data_array['description']))
  1439. {
  1440. $this->errors[] = '[description] not specified for ' . $frame_name;
  1441. }
  1442. elseif (in_array($frame_name . $source_data_array['description'], $PreviousFrames))
  1443. {
  1444. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Description (' . $source_data_array['description'] . ')';
  1445. }
  1446. else
  1447. {
  1448. $PreviousFrames[] = $frame_name . $source_data_array['description'];
  1449. }
  1450. break;
  1451. case 'USER' :
  1452. if (! isset($source_data_array['language']))
  1453. {
  1454. $this->errors[] = '[language] not specified for ' . $frame_name;
  1455. }
  1456. elseif (in_array($frame_name . $source_data_array['language'], $PreviousFrames))
  1457. {
  1458. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Language (' . $source_data_array['language'] . ')';
  1459. }
  1460. else
  1461. {
  1462. $PreviousFrames[] = $frame_name . $source_data_array['language'];
  1463. }
  1464. break;
  1465. case 'USLT' :
  1466. case 'SYLT' :
  1467. case 'COMM' :
  1468. if (! isset($source_data_array['language']))
  1469. {
  1470. $this->errors[] = '[language] not specified for ' . $frame_name;
  1471. }
  1472. elseif (! isset($source_data_array['description']))
  1473. {
  1474. $this->errors[] = '[description] not specified for ' . $frame_name;
  1475. }
  1476. elseif (in_array($frame_name . $source_data_array['language'] . $source_data_array['description'], $PreviousFrames))
  1477. {
  1478. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Language + Description (' . $source_data_array['language'] . ' + ' . $source_data_array['description'] . ')';
  1479. }
  1480. else
  1481. {
  1482. $PreviousFrames[] = $frame_name . $source_data_array['language'] . $source_data_array['description'];
  1483. }
  1484. break;
  1485. case 'POPM' :
  1486. if (! isset($source_data_array['email']))
  1487. {
  1488. $this->errors[] = '[email] not specified for ' . $frame_name;
  1489. }
  1490. elseif (in_array($frame_name . $source_data_array['email'], $PreviousFrames))
  1491. {
  1492. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Email (' . $source_data_array['email'] . ')';
  1493. }
  1494. else
  1495. {
  1496. $PreviousFrames[] = $frame_name . $source_data_array['email'];
  1497. }
  1498. break;
  1499. case 'IPLS' :
  1500. case 'MCDI' :
  1501. case 'ETCO' :
  1502. case 'MLLT' :
  1503. case 'SYTC' :
  1504. case 'RVRB' :
  1505. case 'PCNT' :
  1506. case 'RBUF' :
  1507. case 'POSS' :
  1508. case 'OWNE' :
  1509. case 'SEEK' :
  1510. case 'ASPI' :
  1511. case 'RGAD' :
  1512. if (in_array($frame_name, $PreviousFrames))
  1513. {
  1514. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed';
  1515. }
  1516. else
  1517. {
  1518. $PreviousFrames[] = $frame_name;
  1519. }
  1520. break;
  1521. case 'LINK' :
  1522. // this isn't implemented quite right (yet) - it should check the target frame data for compliance
  1523. // but right now it just allows one linked frame of each type, to be safe.
  1524. if (! isset($source_data_array['frameid']))
  1525. {
  1526. $this->errors[] = '[frameid] not specified for ' . $frame_name;
  1527. }
  1528. elseif (in_array($frame_name . $source_data_array['frameid'], $PreviousFrames))
  1529. {
  1530. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same FrameID (' . $source_data_array['frameid'] . ')';
  1531. }
  1532. elseif (in_array($source_data_array['frameid'], $PreviousFrames))
  1533. {
  1534. // no links to singleton tags
  1535. $this->errors[] = 'Cannot specify a ' . $frame_name . ' tag to a singleton tag that already exists (' . $source_data_array['frameid'] . ')';
  1536. }
  1537. else
  1538. {
  1539. $PreviousFrames[] = $frame_name . $source_data_array['frameid']; // only one linked tag of this type
  1540. $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type
  1541. }
  1542. break;
  1543. case 'COMR' :
  1544. // There may be more than one 'commercial frame' in a tag, but no two may be identical
  1545. // Checking isn't implemented at all (yet) - just assumes that it's OK.
  1546. break;
  1547. case 'PRIV' :
  1548. case 'SIGN' :
  1549. if (! isset($source_data_array['ownerid']))
  1550. {
  1551. $this->errors[] = '[ownerid] not specified for ' . $frame_name;
  1552. }
  1553. elseif (! isset($source_data_array['data']))
  1554. {
  1555. $this->errors[] = '[data] not specified for ' . $frame_name;
  1556. }
  1557. elseif (in_array($frame_name . $source_data_array['ownerid'] . $source_data_array['data'], $PreviousFrames))
  1558. {
  1559. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same OwnerID + Data (' . $source_data_array['ownerid'] . ' + ' . $source_data_array['data'] . ')';
  1560. }
  1561. else
  1562. {
  1563. $PreviousFrames[] = $frame_name . $source_data_array['ownerid'] . $source_data_array['data'];
  1564. }
  1565. break;
  1566. default :
  1567. if (($frame_name{0} != 'T') && ($frame_name{0} != 'W'))
  1568. {
  1569. $this->errors[] = 'Frame not allowed in ID3v2.' . $this->majorversion . ': ' . $frame_name;
  1570. }
  1571. break;
  1572. }
  1573. }
  1574. elseif ($this->majorversion == 3)
  1575. {
  1576. switch ($frame_name)
  1577. {
  1578. case 'UFID' :
  1579. case 'AENC' :
  1580. case 'ENCR' :
  1581. case 'GRID' :
  1582. if (! isset($source_data_array['ownerid']))
  1583. {
  1584. $this->errors[] = '[ownerid] not specified for ' . $frame_name;
  1585. }
  1586. elseif (in_array($frame_name . $source_data_array['ownerid'], $PreviousFrames))
  1587. {
  1588. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same OwnerID (' . $source_data_array['ownerid'] . ')';
  1589. }
  1590. else
  1591. {
  1592. $PreviousFrames[] = $frame_name . $source_data_array['ownerid'];
  1593. }
  1594. break;
  1595. case 'TXXX' :
  1596. case 'WXXX' :
  1597. case 'APIC' :
  1598. case 'GEOB' :
  1599. if (! isset($source_data_array['description']))
  1600. {
  1601. $this->errors[] = '[description] not specified for ' . $frame_name;
  1602. }
  1603. elseif (in_array($frame_name . $source_data_array['description'], $PreviousFrames))
  1604. {
  1605. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Description (' . $source_data_array['description'] . ')';
  1606. }
  1607. else
  1608. {
  1609. $PreviousFrames[] = $frame_name . $source_data_array['description'];
  1610. }
  1611. break;
  1612. case 'USER' :
  1613. if (! isset($source_data_array['language']))
  1614. {
  1615. $this->errors[] = '[language] not specified for ' . $frame_name;
  1616. }
  1617. elseif (in_array($frame_name . $source_data_array['language'], $PreviousFrames))
  1618. {
  1619. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Language (' . $source_data_array['language'] . ')';
  1620. }
  1621. else
  1622. {
  1623. $PreviousFrames[] = $frame_name . $source_data_array['language'];
  1624. }
  1625. break;
  1626. case 'USLT' :
  1627. case 'SYLT' :
  1628. case 'COMM' :
  1629. if (! isset($source_data_array['language']))
  1630. {
  1631. $this->errors[] = '[language] not specified for ' . $frame_name;
  1632. }
  1633. elseif (! isset($source_data_array['description']))
  1634. {
  1635. $this->errors[] = '[description] not specified for ' . $frame_name;
  1636. }
  1637. elseif (in_array($frame_name . $source_data_array['language'] . $source_data_array['description'], $PreviousFrames))
  1638. {
  1639. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Language + Description (' . $source_data_array['language'] . ' + ' . $source_data_array['description'] . ')';
  1640. }
  1641. else
  1642. {
  1643. $PreviousFrames[] = $frame_name . $source_data_array['language'] . $source_data_array['description'];
  1644. }
  1645. break;
  1646. case 'POPM' :
  1647. if (! isset($source_data_array['email']))
  1648. {
  1649. $this->errors[] = '[email] not specified for ' . $frame_name;
  1650. }
  1651. elseif (in_array($frame_name . $source_data_array['email'], $PreviousFrames))
  1652. {
  1653. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Email (' . $source_data_array['email'] . ')';
  1654. }
  1655. else
  1656. {
  1657. $PreviousFrames[] = $frame_name . $source_data_array['email'];
  1658. }
  1659. break;
  1660. case 'IPLS' :
  1661. case 'MCDI' :
  1662. case 'ETCO' :
  1663. case 'MLLT' :
  1664. case 'SYTC' :
  1665. case 'RVAD' :
  1666. case 'EQUA' :
  1667. case 'RVRB' :
  1668. case 'PCNT' :
  1669. case 'RBUF' :
  1670. case 'POSS' :
  1671. case 'OWNE' :
  1672. case 'RGAD' :
  1673. if (in_array($frame_name, $PreviousFrames))
  1674. {
  1675. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed';
  1676. }
  1677. else
  1678. {
  1679. $PreviousFrames[] = $frame_name;
  1680. }
  1681. break;
  1682. case 'LINK' :
  1683. // this isn't implemented quite right (yet) - it should check the target frame data for compliance
  1684. // but right now it just allows one linked frame of each type, to be safe.
  1685. if (! isset($source_data_array['frameid']))
  1686. {
  1687. $this->errors[] = '[frameid] not specified for ' . $frame_name;
  1688. }
  1689. elseif (in_array($frame_name . $source_data_array['frameid'], $PreviousFrames))
  1690. {
  1691. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same FrameID (' . $source_data_array['frameid'] . ')';
  1692. }
  1693. elseif (in_array($source_data_array['frameid'], $PreviousFrames))
  1694. {
  1695. // no links to singleton tags
  1696. $this->errors[] = 'Cannot specify a ' . $frame_name . ' tag to a singleton tag that already exists (' . $source_data_array['frameid'] . ')';
  1697. }
  1698. else
  1699. {
  1700. $PreviousFrames[] = $frame_name . $source_data_array['frameid']; // only one linked tag of this type
  1701. $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type
  1702. }
  1703. break;
  1704. case 'COMR' :
  1705. // There may be more than one 'commercial frame' in a tag, but no two may be identical
  1706. // Checking isn't implemented at all (yet) - just assumes that it's OK.
  1707. break;
  1708. case 'PRIV' :
  1709. if (! isset($source_data_array['ownerid']))
  1710. {
  1711. $this->errors[] = '[ownerid] not specified for ' . $frame_name;
  1712. }
  1713. elseif (! isset($source_data_array['data']))
  1714. {
  1715. $this->errors[] = '[data] not specified for ' . $frame_name;
  1716. }
  1717. elseif (in_array($frame_name . $source_data_array['ownerid'] . $source_data_array['data'], $PreviousFrames))
  1718. {
  1719. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same OwnerID + Data (' . $source_data_array['ownerid'] . ' + ' . $source_data_array['data'] . ')';
  1720. }
  1721. else
  1722. {
  1723. $PreviousFrames[] = $frame_name . $source_data_array['ownerid'] . $source_data_array['data'];
  1724. }
  1725. break;
  1726. default :
  1727. if (($frame_name{0} != 'T') && ($frame_name{0} != 'W'))
  1728. {
  1729. $this->errors[] = 'Frame not allowed in ID3v2.' . $this->majorversion . ': ' . $frame_name;
  1730. }
  1731. break;
  1732. }
  1733. }
  1734. elseif ($this->majorversion == 2)
  1735. {
  1736. switch ($frame_name)
  1737. {
  1738. case 'UFI' :
  1739. case 'CRM' :
  1740. case 'CRA' :
  1741. if (! isset($source_data_array['ownerid']))
  1742. {
  1743. $this->errors[] = '[ownerid] not specified for ' . $frame_name;
  1744. }
  1745. elseif (in_array($frame_name . $source_data_array['ownerid'], $PreviousFrames))
  1746. {
  1747. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same OwnerID (' . $source_data_array['ownerid'] . ')';
  1748. }
  1749. else
  1750. {
  1751. $PreviousFrames[] = $frame_name . $source_data_array['ownerid'];
  1752. }
  1753. break;
  1754. case 'TXX' :
  1755. case 'WXX' :
  1756. case 'PIC' :
  1757. case 'GEO' :
  1758. if (! isset($source_data_array['description']))
  1759. {
  1760. $this->errors[] = '[description] not specified for ' . $frame_name;
  1761. }
  1762. elseif (in_array($frame_name . $source_data_array['description'], $PreviousFrames))
  1763. {
  1764. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Description (' . $source_data_array['description'] . ')';
  1765. }
  1766. else
  1767. {
  1768. $PreviousFrames[] = $frame_name . $source_data_array['description'];
  1769. }
  1770. break;
  1771. case 'ULT' :
  1772. case 'SLT' :
  1773. case 'COM' :
  1774. if (! isset($source_data_array['language']))
  1775. {
  1776. $this->errors[] = '[language] not specified for ' . $frame_name;
  1777. }
  1778. elseif (! isset($source_data_array['description']))
  1779. {
  1780. $this->errors[] = '[description] not specified for ' . $frame_name;
  1781. }
  1782. elseif (in_array($frame_name . $source_data_array['language'] . $source_data_array['description'], $PreviousFrames))
  1783. {
  1784. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Language + Description (' . $source_data_array['language'] . ' + ' . $source_data_array['description'] . ')';
  1785. }
  1786. else
  1787. {
  1788. $PreviousFrames[] = $frame_name . $source_data_array['language'] . $source_data_array['description'];
  1789. }
  1790. break;
  1791. case 'POP' :
  1792. if (! isset($source_data_array['email']))
  1793. {
  1794. $this->errors[] = '[email] not specified for ' . $frame_name;
  1795. }
  1796. elseif (in_array($frame_name . $source_data_array['email'], $PreviousFrames))
  1797. {
  1798. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same Email (' . $source_data_array['email'] . ')';
  1799. }
  1800. else
  1801. {
  1802. $PreviousFrames[] = $frame_name . $source_data_array['email'];
  1803. }
  1804. break;
  1805. case 'IPL' :
  1806. case 'MCI' :
  1807. case 'ETC' :
  1808. case 'MLL' :
  1809. case 'STC' :
  1810. case 'RVA' :
  1811. case 'EQU' :
  1812. case 'REV' :
  1813. case 'CNT' :
  1814. case 'BUF' :
  1815. if (in_array($frame_name, $PreviousFrames))
  1816. {
  1817. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed';
  1818. }
  1819. else
  1820. {
  1821. $PreviousFrames[] = $frame_name;
  1822. }
  1823. break;
  1824. case 'LNK' :
  1825. // this isn't implemented quite right (yet) - it should check the target frame data for compliance
  1826. // but right now it just allows one linked frame of each type, to be safe.
  1827. if (! isset($source_data_array['frameid']))
  1828. {
  1829. $this->errors[] = '[frameid] not specified for ' . $frame_name;
  1830. }
  1831. elseif (in_array($frame_name . $source_data_array['frameid'], $PreviousFrames))
  1832. {
  1833. $this->errors[] = 'Only one ' . $frame_name . ' tag allowed with the same FrameID (' . $source_data_array['frameid'] . ')';
  1834. }
  1835. elseif (in_array($source_data_array['frameid'], $PreviousFrames))
  1836. {
  1837. // no links to singleton tags
  1838. $this->errors[] = 'Cannot specify a ' . $frame_name . ' tag to a singleton tag that already exists (' . $source_data_array['frameid'] . ')';
  1839. }
  1840. else
  1841. {
  1842. $PreviousFrames[] = $frame_name . $source_data_array['frameid']; // only one linked tag of this type
  1843. $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type
  1844. }
  1845. break;
  1846. default :
  1847. if (($frame_name{0} != 'T') && ($frame_name{0} != 'W'))
  1848. {
  1849. $this->errors[] = 'Frame not allowed in ID3v2.' . $this->majorversion . ': ' . $frame_name;
  1850. }
  1851. break;
  1852. }
  1853. }
  1854. if (! empty($this->errors))
  1855. {
  1856. return false;
  1857. }
  1858. return true;
  1859. }
  1860. function GenerateID3v2Tag($noerrorsonly = true)
  1861. {
  1862. $this->ID3v2FrameIsAllowed(null, ''); // clear static array in case this isn't the first call to $this->GenerateID3v2Tag()
  1863. $tagstring = '';
  1864. if (is_array($this->tag_data))
  1865. {
  1866. foreach ($this->tag_data as $frame_name => $frame_rawinputdata)
  1867. {
  1868. foreach ($frame_rawinputdata as $irrelevantindex => $source_data_array)
  1869. {
  1870. if (getid3_id3v2 :: IsValidID3v2FrameName($frame_name, $this->majorversion))
  1871. {
  1872. unset($frame_length);
  1873. unset($frame_flags);
  1874. $frame_data = false;
  1875. if ($this->ID3v2FrameIsAllowed($frame_name, $source_data_array))
  1876. {
  1877. if ($frame_data = $this->GenerateID3v2FrameData($frame_name, $source_data_array))
  1878. {
  1879. $FrameUnsynchronisation = false;
  1880. if ($this->majorversion >= 4)
  1881. {
  1882. // frame-level unsynchronisation
  1883. $unsynchdata = $frame_data;
  1884. if ($this->id3v2_use_unsynchronisation)
  1885. {
  1886. $unsynchdata = $this->Unsynchronise($frame_data);
  1887. }
  1888. if (strlen($unsynchdata) != strlen($frame_data))
  1889. {
  1890. // unsynchronisation needed
  1891. $FrameUnsynchronisation = true;
  1892. $frame_data = $unsynchdata;
  1893. if (isset($TagUnsynchronisation) && $TagUnsynchronisation === false)
  1894. {
  1895. // only set to true if ALL frames are unsynchronised
  1896. }
  1897. else
  1898. {
  1899. $TagUnsynchronisation = true;
  1900. }
  1901. }
  1902. else
  1903. {
  1904. if (isset($TagUnsynchronisation))
  1905. {
  1906. $TagUnsynchronisation = false;
  1907. }
  1908. }
  1909. unset($unsynchdata);
  1910. $frame_length = getid3_lib :: BigEndian2String(strlen($frame_data), 4, true);
  1911. }
  1912. else
  1913. {
  1914. $frame_length = getid3_lib :: BigEndian2String(strlen($frame_data), 4, false);
  1915. }
  1916. $frame_flags = $this->GenerateID3v2FrameFlags($this->ID3v2FrameFlagsLookupTagAlter($frame_name), $this->ID3v2FrameFlagsLookupFileAlter($frame_name), false, false, false, false, $FrameUnsynchronisation, false);
  1917. }
  1918. }
  1919. else
  1920. {
  1921. $this->errors[] = 'Frame "' . $frame_name . '" is NOT allowed';
  1922. }
  1923. if ($frame_data === false)
  1924. {
  1925. $this->errors[] = '$this->GenerateID3v2FrameData() failed for "' . $frame_name . '"';
  1926. if ($noerrorsonly)
  1927. {
  1928. return false;
  1929. }
  1930. else
  1931. {
  1932. unset($frame_name);
  1933. }
  1934. }
  1935. }
  1936. else
  1937. {
  1938. // ignore any invalid frame names, including 'title', 'header', etc
  1939. $this->warnings[] = 'Ignoring invalid ID3v2 frame type: "' . $frame_name . '"';
  1940. unset($frame_name);
  1941. unset($frame_length);
  1942. unset($frame_flags);
  1943. unset($frame_data);
  1944. }
  1945. if (isset($frame_name) && isset($frame_length) && isset($frame_flags) && isset($frame_data))
  1946. {
  1947. $tagstring .= $frame_name . $frame_length . $frame_flags . $frame_data;
  1948. }
  1949. }
  1950. }
  1951. if (! isset($TagUnsynchronisation))
  1952. {
  1953. $TagUnsynchronisation = false;
  1954. }
  1955. if (($this->majorversion <= 3) && $this->id3v2_use_unsynchronisation)
  1956. {
  1957. // tag-level unsynchronisation
  1958. $unsynchdata = $this->Unsynchronise($tagstring);
  1959. if (strlen($unsynchdata) != strlen($tagstring))
  1960. {
  1961. // unsynchronisation needed
  1962. $TagUnsynchronisation = true;
  1963. $tagstring = $unsynchdata;
  1964. }
  1965. }
  1966. while ($this->paddedlength < (strlen($tagstring) + getid3_id3v2 :: ID3v2HeaderLength($this->majorversion)))
  1967. {
  1968. $this->paddedlength += 1024;
  1969. }
  1970. $footer = false; // ID3v2 footers not yet supported in getID3()
  1971. if (! $footer && ($this->paddedlength > (strlen($tagstring) + getid3_id3v2 :: ID3v2HeaderLength($this->majorversion))))
  1972. {
  1973. // pad up to $paddedlength bytes if unpadded tag is shorter than $paddedlength
  1974. // "Furthermore it MUST NOT have any padding when a tag footer is added to the tag."
  1975. $tagstring .= @str_repeat("\x00", $this->paddedlength - strlen($tagstring) - getid3_id3v2 :: ID3v2HeaderLength($this->majorversion));
  1976. }
  1977. if ($this->id3v2_use_unsynchronisation && (substr($tagstring, strlen($tagstring) - 1, 1) == "\xFF"))
  1978. {
  1979. // special unsynchronisation case:
  1980. // if last byte == $FF then appended a $00
  1981. $TagUnsynchronisation = true;
  1982. $tagstring .= "\x00";
  1983. }
  1984. $tagheader = 'ID3';
  1985. $tagheader .= chr($this->majorversion);
  1986. $tagheader .= chr($this->minorversion);
  1987. $tagheader .= $this->GenerateID3v2TagFlags(array('unsynchronisation' => $TagUnsynchronisation));
  1988. $tagheader .= getid3_lib :: BigEndian2String(strlen($tagstring), 4, true);
  1989. return $tagheader . $tagstring;
  1990. }
  1991. $this->errors[] = 'tag_data is not an array in GenerateID3v2Tag()';
  1992. return false;
  1993. }
  1994. function ID3v2IsValidPriceString($pricestring)
  1995. {
  1996. if (getid3_id3v2 :: LanguageLookup(substr($pricestring, 0, 3), true) == '')
  1997. {
  1998. return false;
  1999. }
  2000. elseif (! $this->IsANumber(substr($pricestring, 3), true))
  2001. {
  2002. return false;
  2003. }
  2004. return true;
  2005. }
  2006. function ID3v2FrameFlagsLookupTagAlter($framename)
  2007. {
  2008. // unfinished
  2009. switch ($framename)
  2010. {
  2011. case 'RGAD' :
  2012. $allow = true;
  2013. default :
  2014. $allow = false;
  2015. break;
  2016. }
  2017. return $allow;
  2018. }
  2019. function ID3v2FrameFlagsLookupFileAlter($framename)
  2020. {
  2021. // unfinished
  2022. switch ($framename)
  2023. {
  2024. case 'RGAD' :
  2025. return false;
  2026. break;
  2027. default :
  2028. return false;
  2029. break;
  2030. }
  2031. }
  2032. function ID3v2IsValidETCOevent($eventid)
  2033. {
  2034. if (($eventid < 0) || ($eventid > 0xFF))
  2035. {
  2036. // outside range of 1 byte
  2037. return false;
  2038. }
  2039. elseif (($eventid >= 0xF0) && ($eventid <= 0xFC))
  2040. {
  2041. // reserved for future use
  2042. return false;
  2043. }
  2044. elseif (($eventid >= 0x17) && ($eventid <= 0xDF))
  2045. {
  2046. // reserved for future use
  2047. return false;
  2048. }
  2049. elseif (($eventid >= 0x0E) && ($eventid <= 0x16) && ($this->majorversion == 2))
  2050. {
  2051. // not defined in ID3v2.2
  2052. return false;
  2053. }
  2054. elseif (($eventid >= 0x15) && ($eventid <= 0x16) && ($this->majorversion == 3))
  2055. {
  2056. // not defined in ID3v2.3
  2057. return false;
  2058. }
  2059. return true;
  2060. }
  2061. function ID3v2IsValidSYLTtype($contenttype)
  2062. {
  2063. if (($contenttype >= 0) && ($contenttype <= 8) && ($this->majorversion == 4))
  2064. {
  2065. return true;
  2066. }
  2067. elseif (($contenttype >= 0) && ($contenttype <= 6) && ($this->majorversion == 3))
  2068. {
  2069. return true;
  2070. }
  2071. return false;
  2072. }
  2073. function ID3v2IsValidRVA2channeltype($channeltype)
  2074. {
  2075. if (($channeltype >= 0) && ($channeltype <= 8) && ($this->majorversion == 4))
  2076. {
  2077. return true;
  2078. }
  2079. return false;
  2080. }
  2081. function ID3v2IsValidAPICpicturetype($picturetype)
  2082. {
  2083. if (($picturetype >= 0) && ($picturetype <= 0x14) && ($this->majorversion >= 2) && ($this->majorversion <= 4))
  2084. {
  2085. return true;
  2086. }
  2087. return false;
  2088. }
  2089. function ID3v2IsValidAPICimageformat($imageformat)
  2090. {
  2091. if ($imageformat == '-->')
  2092. {
  2093. return true;
  2094. }
  2095. elseif ($this->majorversion == 2)
  2096. {
  2097. if ((strlen($imageformat) == 3) && ($imageformat == strtoupper($imageformat)))
  2098. {
  2099. return true;
  2100. }
  2101. }
  2102. elseif (($this->majorversion == 3) || ($this->majorversion == 4))
  2103. {
  2104. if ($this->IsValidMIMEstring($imageformat))
  2105. {
  2106. return true;
  2107. }
  2108. }
  2109. return false;
  2110. }
  2111. function ID3v2IsValidCOMRreceivedAs($receivedas)
  2112. {
  2113. if (($this->majorversion >= 3) && ($receivedas >= 0) && ($receivedas <= 8))
  2114. {
  2115. return true;
  2116. }
  2117. return false;
  2118. }
  2119. function ID3v2IsValidRGADname($RGADname)
  2120. {
  2121. if (($RGADname >= 0) && ($RGADname <= 2))
  2122. {
  2123. return true;
  2124. }
  2125. return false;
  2126. }
  2127. function ID3v2IsValidRGADoriginator($RGADoriginator)
  2128. {
  2129. if (($RGADoriginator >= 0) && ($RGADoriginator <= 3))
  2130. {
  2131. return true;
  2132. }
  2133. return false;
  2134. }
  2135. function ID3v2IsValidTextEncoding($textencodingbyte)
  2136. {
  2137. static $ID3v2IsValidTextEncoding_cache = array(2 => array(true, true), 3 => array(true, true),
  2138. 4 => array(true, true, true, true));
  2139. return isset($ID3v2IsValidTextEncoding_cache[$this->majorversion][$textencodingbyte]);
  2140. }
  2141. function Unsynchronise($data)
  2142. {
  2143. // Whenever a false synchronisation is found within the tag, one zeroed
  2144. // byte is inserted after the first false synchronisation byte. The
  2145. // format of a correct sync that should be altered by ID3 encoders is as
  2146. // follows:
  2147. // %11111111 111xxxxx
  2148. // And should be replaced with:
  2149. // %11111111 00000000 111xxxxx
  2150. // This has the side effect that all $FF 00 combinations have to be
  2151. // altered, so they won't be affected by the decoding process. Therefore
  2152. // all the $FF 00 combinations have to be replaced with the $FF 00 00
  2153. // combination during the unsynchronisation.
  2154. $data = str_replace("\xFF\x00", "\xFF\x00\x00", $data);
  2155. $unsyncheddata = '';
  2156. $datalength = strlen($data);
  2157. for($i = 0; $i < $datalength; $i ++)
  2158. {
  2159. $thischar = $data{$i};
  2160. $unsyncheddata .= $thischar;
  2161. if ($thischar == "\xFF")
  2162. {
  2163. $nextchar = ord($data{$i + 1});
  2164. if (($nextchar & 0xE0) == 0xE0)
  2165. {
  2166. // previous byte = 11111111, this byte = 111?????
  2167. $unsyncheddata .= "\x00";
  2168. }
  2169. }
  2170. }
  2171. return $unsyncheddata;
  2172. }
  2173. function is_hash($var)
  2174. {
  2175. // written by dev-null�christophe*vg
  2176. // taken from http://www.php.net/manual/en/function.array-merge-recursive.php
  2177. if (is_array($var))
  2178. {
  2179. $keys = array_keys($var);
  2180. $all_num = true;
  2181. for($i = 0; $i < count($keys); $i ++)
  2182. {
  2183. if (is_string($keys[$i]))
  2184. {
  2185. return true;
  2186. }
  2187. }
  2188. }
  2189. return false;
  2190. }
  2191. function array_join_merge($arr1, $arr2)
  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($arr1) && is_array($arr2))
  2196. {
  2197. // the same -> merge
  2198. $new_array = array();
  2199. if ($this->is_hash($arr1) && $this->is_hash($arr2))
  2200. {
  2201. // hashes -> merge based on keys
  2202. $keys = array_merge(array_keys($arr1), array_keys($arr2));
  2203. foreach ($keys as $key)
  2204. {
  2205. $new_array[$key] = $this->array_join_merge(@$arr1[$key], @$arr2[$key]);
  2206. }
  2207. }
  2208. else
  2209. {
  2210. // two real arrays -> merge
  2211. $new_array = array_reverse(array_unique(array_reverse(array_merge($arr1, $arr2))));
  2212. }
  2213. return $new_array;
  2214. }
  2215. else
  2216. {
  2217. // not the same ... take new one if defined, else the old one stays
  2218. return $arr2 ? $arr2 : $arr1;
  2219. }
  2220. }
  2221. function IsValidMIMEstring($mimestring)
  2222. {
  2223. if ((strlen($mimestring) >= 3) && (strpos($mimestring, '/') > 0) && (strpos($mimestring, '/') < (strlen($mimestring) - 1)))
  2224. {
  2225. return true;
  2226. }
  2227. return false;
  2228. }
  2229. function IsWithinBitRange($number, $maxbits, $signed = false)
  2230. {
  2231. if ($signed)
  2232. {
  2233. if (($number > (0 - pow(2, $maxbits - 1))) && ($number <= pow(2, $maxbits - 1)))
  2234. {
  2235. return true;
  2236. }
  2237. }
  2238. else
  2239. {
  2240. if (($number >= 0) && ($number <= pow(2, $maxbits)))
  2241. {
  2242. return true;
  2243. }
  2244. }
  2245. return false;
  2246. }
  2247. function safe_parse_url($url)
  2248. {
  2249. $parts = @parse_url($url);
  2250. $parts['scheme'] = (isset($parts['scheme']) ? $parts['scheme'] : '');
  2251. $parts['host'] = (isset($parts['host']) ? $parts['host'] : '');
  2252. $parts['user'] = (isset($parts['user']) ? $parts['user'] : '');
  2253. $parts['pass'] = (isset($parts['pass']) ? $parts['pass'] : '');
  2254. $parts['path'] = (isset($parts['path']) ? $parts['path'] : '');
  2255. $parts['query'] = (isset($parts['query']) ? $parts['query'] : '');
  2256. return $parts;
  2257. }
  2258. function IsValidURL($url, $allowUserPass = false)
  2259. {
  2260. if ($url == '')
  2261. {
  2262. return false;
  2263. }
  2264. if ($allowUserPass !== true)
  2265. {
  2266. if (strstr($url, '@'))
  2267. {
  2268. // in the format http://user:pass@example.com or http://user@example.com
  2269. // but could easily be somebody incorrectly entering an email address in place of a URL
  2270. return false;
  2271. }
  2272. }
  2273. if ($parts = $this->safe_parse_url($url))
  2274. {
  2275. if (($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') && ($parts['scheme'] != 'ftp') && ($parts['scheme'] != 'gopher'))
  2276. {
  2277. return false;
  2278. }
  2279. elseif (! eregi("^[[:alnum:]]([-.]?[0-9a-z])*\.[a-z]{2,3}$", $parts['host'], $regs) && ! IsValidDottedIP($parts['host']))
  2280. {
  2281. return false;
  2282. }
  2283. elseif (! eregi("^([[:alnum:]-]|[\_])*$", $parts['user'], $regs))
  2284. {
  2285. return false;
  2286. }
  2287. elseif (! eregi("^([[:alnum:]-]|[\_])*$", $parts['pass'], $regs))
  2288. {
  2289. return false;
  2290. }
  2291. elseif (! eregi("^[[:alnum:]/_\.@~-]*$", $parts['path'], $regs))
  2292. {
  2293. return false;
  2294. }
  2295. elseif (! eregi("^[[:alnum:]?&=+:;_()%#/,\.-]*$", $parts['query'], $regs))
  2296. {
  2297. return false;
  2298. }
  2299. else
  2300. {
  2301. return true;
  2302. }
  2303. }
  2304. return false;
  2305. }
  2306. function ID3v2ShortFrameNameLookup($majorversion, $long_description)
  2307. {
  2308. $long_description = str_replace(' ', '_', strtolower(trim($long_description)));
  2309. static $ID3v2ShortFrameNameLookup = array();
  2310. if (empty($ID3v2ShortFrameNameLookup))
  2311. {
  2312. // The following are unique to ID3v2.2
  2313. $ID3v2ShortFrameNameLookup[2]['comment'] = 'COM';
  2314. $ID3v2ShortFrameNameLookup[2]['album'] = 'TAL';
  2315. $ID3v2ShortFrameNameLookup[2]['beats_per_minute'] = 'TBP';
  2316. $ID3v2ShortFrameNameLookup[2]['composer'] = 'TCM';
  2317. $ID3v2ShortFrameNameLookup[2]['genre'] = 'TCO';
  2318. $ID3v2ShortFrameNameLookup[2]['copyright'] = 'TCR';
  2319. $ID3v2ShortFrameNameLookup[2]['encoded_by'] = 'TEN';
  2320. $ID3v2ShortFrameNameLookup[2]['language'] = 'TLA';
  2321. $ID3v2ShortFrameNameLookup[2]['length'] = 'TLE';
  2322. $ID3v2ShortFrameNameLookup[2]['original_artist'] = 'TOA';
  2323. $ID3v2ShortFrameNameLookup[2]['original_filename'] = 'TOF';
  2324. $ID3v2ShortFrameNameLookup[2]['original_lyricist'] = 'TOL';
  2325. $ID3v2ShortFrameNameLookup[2]['original_album_title'] = 'TOT';
  2326. $ID3v2ShortFrameNameLookup[2]['artist'] = 'TP1';
  2327. $ID3v2ShortFrameNameLookup[2]['band'] = 'TP2';
  2328. $ID3v2ShortFrameNameLookup[2]['conductor'] = 'TP3';
  2329. $ID3v2ShortFrameNameLookup[2]['remixer'] = 'TP4';
  2330. $ID3v2ShortFrameNameLookup[2]['publisher'] = 'TPB';
  2331. $ID3v2ShortFrameNameLookup[2]['isrc'] = 'TRC';
  2332. $ID3v2ShortFrameNameLookup[2]['tracknumber'] = 'TRK';
  2333. $ID3v2ShortFrameNameLookup[2]['size'] = 'TSI';
  2334. $ID3v2ShortFrameNameLookup[2]['encoder_settings'] = 'TSS';
  2335. $ID3v2ShortFrameNameLookup[2]['description'] = 'TT1';
  2336. $ID3v2ShortFrameNameLookup[2]['title'] = 'TT2';
  2337. $ID3v2ShortFrameNameLookup[2]['subtitle'] = 'TT3';
  2338. $ID3v2ShortFrameNameLookup[2]['lyricist'] = 'TXT';
  2339. $ID3v2ShortFrameNameLookup[2]['user_text'] = 'TXX';
  2340. $ID3v2ShortFrameNameLookup[2]['year'] = 'TYE';
  2341. $ID3v2ShortFrameNameLookup[2]['unique_file_identifier'] = 'UFI';
  2342. $ID3v2ShortFrameNameLookup[2]['unsynchronised_lyrics'] = 'ULT';
  2343. $ID3v2ShortFrameNameLookup[2]['url_file'] = 'WAF';
  2344. $ID3v2ShortFrameNameLookup[2]['url_artist'] = 'WAR';
  2345. $ID3v2ShortFrameNameLookup[2]['url_source'] = 'WAS';
  2346. $ID3v2ShortFrameNameLookup[2]['copyright_information'] = 'WCP';
  2347. $ID3v2ShortFrameNameLookup[2]['url_publisher'] = 'WPB';
  2348. $ID3v2ShortFrameNameLookup[2]['url_user'] = 'WXX';
  2349. // The following are common to ID3v2.3 and ID3v2.4
  2350. $ID3v2ShortFrameNameLookup[3]['audio_encryption'] = 'AENC';
  2351. $ID3v2ShortFrameNameLookup[3]['attached_picture'] = 'APIC';
  2352. $ID3v2ShortFrameNameLookup[3]['comment'] = 'COMM';
  2353. $ID3v2ShortFrameNameLookup[3]['commercial'] = 'COMR';
  2354. $ID3v2ShortFrameNameLookup[3]['encryption_method_registration'] = 'ENCR';
  2355. $ID3v2ShortFrameNameLookup[3]['event_timing_codes'] = 'ETCO';
  2356. $ID3v2ShortFrameNameLookup[3]['general_encapsulated_object'] = 'GEOB';
  2357. $ID3v2ShortFrameNameLookup[3]['group_identification_registration'] = 'GRID';
  2358. $ID3v2ShortFrameNameLookup[3]['linked_information'] = 'LINK';
  2359. $ID3v2ShortFrameNameLookup[3]['music_cd_identifier'] = 'MCDI';
  2360. $ID3v2ShortFrameNameLookup[3]['mpeg_location_lookup_table'] = 'MLLT';
  2361. $ID3v2ShortFrameNameLookup[3]['ownership'] = 'OWNE';
  2362. $ID3v2ShortFrameNameLookup[3]['play_counter'] = 'PCNT';
  2363. $ID3v2ShortFrameNameLookup[3]['popularimeter'] = 'POPM';
  2364. $ID3v2ShortFrameNameLookup[3]['position_synchronisation'] = 'POSS';
  2365. $ID3v2ShortFrameNameLookup[3]['private'] = 'PRIV';
  2366. $ID3v2ShortFrameNameLookup[3]['recommended_buffer_size'] = 'RBUF';
  2367. $ID3v2ShortFrameNameLookup[3]['reverb'] = 'RVRB';
  2368. $ID3v2ShortFrameNameLookup[3]['synchronised_lyrics'] = 'SYLT';
  2369. $ID3v2ShortFrameNameLookup[3]['synchronised_tempo_codes'] = 'SYTC';
  2370. $ID3v2ShortFrameNameLookup[3]['album'] = 'TALB';
  2371. $ID3v2ShortFrameNameLookup[3]['beats_per_minute'] = 'TBPM';
  2372. $ID3v2ShortFrameNameLookup[3]['composer'] = 'TCOM';
  2373. $ID3v2ShortFrameNameLookup[3]['genre'] = 'TCON';
  2374. $ID3v2ShortFrameNameLookup[3]['copyright'] = 'TCOP';
  2375. $ID3v2ShortFrameNameLookup[3]['playlist_delay'] = 'TDLY';
  2376. $ID3v2ShortFrameNameLookup[3]['encoded_by'] = 'TENC';
  2377. $ID3v2ShortFrameNameLookup[3]['lyricist'] = 'TEXT';
  2378. $ID3v2ShortFrameNameLookup[3]['file_type'] = 'TFLT';
  2379. $ID3v2ShortFrameNameLookup[3]['content_group_description'] = 'TIT1';
  2380. $ID3v2ShortFrameNameLookup[3]['title'] = 'TIT2';
  2381. $ID3v2ShortFrameNameLookup[3]['subtitle'] = 'TIT3';
  2382. $ID3v2ShortFrameNameLookup[3]['initial_key'] = 'TKEY';
  2383. $ID3v2ShortFrameNameLookup[3]['language'] = 'TLAN';
  2384. $ID3v2ShortFrameNameLookup[3]['length'] = 'TLEN';
  2385. $ID3v2ShortFrameNameLookup[3]['media_type'] = 'TMED';
  2386. $ID3v2ShortFrameNameLookup[3]['original_album_title'] = 'TOAL';
  2387. $ID3v2ShortFrameNameLookup[3]['original_filename'] = 'TOFN';
  2388. $ID3v2ShortFrameNameLookup[3]['original_lyricist'] = 'TOLY';
  2389. $ID3v2ShortFrameNameLookup[3]['original_artist'] = 'TOPE';
  2390. $ID3v2ShortFrameNameLookup[3]['file_owner'] = 'TOWN';
  2391. $ID3v2ShortFrameNameLookup[3]['artist'] = 'TPE1';
  2392. $ID3v2ShortFrameNameLookup[3]['band'] = 'TPE2';
  2393. $ID3v2ShortFrameNameLookup[3]['conductor'] = 'TPE3';
  2394. $ID3v2ShortFrameNameLookup[3]['remixer'] = 'TPE4';
  2395. $ID3v2ShortFrameNameLookup[3]['part_of_set'] = 'TPOS';
  2396. $ID3v2ShortFrameNameLookup[3]['publisher'] = 'TPUB';
  2397. $ID3v2ShortFrameNameLookup[3]['tracknumber'] = 'TRCK';
  2398. $ID3v2ShortFrameNameLookup[3]['internet_radio_station_name'] = 'TRSN';
  2399. $ID3v2ShortFrameNameLookup[3]['internet_radio_station_owner'] = 'TRSO';
  2400. $ID3v2ShortFrameNameLookup[3]['isrc'] = 'TSRC';
  2401. $ID3v2ShortFrameNameLookup[3]['encoder_settings'] = 'TSSE';
  2402. $ID3v2ShortFrameNameLookup[3]['user_text'] = 'TXXX';
  2403. $ID3v2ShortFrameNameLookup[3]['unique_file_identifier'] = 'UFID';
  2404. $ID3v2ShortFrameNameLookup[3]['terms_of_use'] = 'USER';
  2405. $ID3v2ShortFrameNameLookup[3]['unsynchronised_lyrics'] = 'USLT';
  2406. $ID3v2ShortFrameNameLookup[3]['commercial'] = 'WCOM';
  2407. $ID3v2ShortFrameNameLookup[3]['copyright_information'] = 'WCOP';
  2408. $ID3v2ShortFrameNameLookup[3]['url_file'] = 'WOAF';
  2409. $ID3v2ShortFrameNameLookup[3]['url_artist'] = 'WOAR';
  2410. $ID3v2ShortFrameNameLookup[3]['url_source'] = 'WOAS';
  2411. $ID3v2ShortFrameNameLookup[3]['url_station'] = 'WORS';
  2412. $ID3v2ShortFrameNameLookup[3]['payment'] = 'WPAY';
  2413. $ID3v2ShortFrameNameLookup[3]['url_publisher'] = 'WPUB';
  2414. $ID3v2ShortFrameNameLookup[3]['url_user'] = 'WXXX';
  2415. // The above are common to ID3v2.3 and ID3v2.4
  2416. // so copy them to ID3v2.4 before adding specifics for 2.3 and 2.4
  2417. $ID3v2ShortFrameNameLookup[4] = $ID3v2ShortFrameNameLookup[3];
  2418. // The following are unique to ID3v2.3
  2419. $ID3v2ShortFrameNameLookup[3]['equalisation'] = 'EQUA';
  2420. $ID3v2ShortFrameNameLookup[3]['involved_people_list'] = 'IPLS';
  2421. $ID3v2ShortFrameNameLookup[3]['relative_volume_adjustment'] = 'RVAD';
  2422. $ID3v2ShortFrameNameLookup[3]['date'] = 'TDAT';
  2423. $ID3v2ShortFrameNameLookup[3]['time'] = 'TIME';
  2424. $ID3v2ShortFrameNameLookup[3]['original_release_year'] = 'TORY';
  2425. $ID3v2ShortFrameNameLookup[3]['recording_dates'] = 'TRDA';
  2426. $ID3v2ShortFrameNameLookup[3]['size'] = 'TSIZ';
  2427. $ID3v2ShortFrameNameLookup[3]['year'] = 'TYER';
  2428. // The following are unique to ID3v2.4
  2429. $ID3v2ShortFrameNameLookup[4]['audio_seek_point_index'] = 'ASPI';
  2430. $ID3v2ShortFrameNameLookup[4]['equalisation'] = 'EQU2';
  2431. $ID3v2ShortFrameNameLookup[4]['relative_volume_adjustment'] = 'RVA2';
  2432. $ID3v2ShortFrameNameLookup[4]['seek'] = 'SEEK';
  2433. $ID3v2ShortFrameNameLookup[4]['signature'] = 'SIGN';
  2434. $ID3v2ShortFrameNameLookup[4]['encoding_time'] = 'TDEN';
  2435. $ID3v2ShortFrameNameLookup[4]['original_release_time'] = 'TDOR';
  2436. $ID3v2ShortFrameNameLookup[4]['recording_time'] = 'TDRC';
  2437. $ID3v2ShortFrameNameLookup[4]['release_time'] = 'TDRL';
  2438. $ID3v2ShortFrameNameLookup[4]['tagging_time'] = 'TDTG';
  2439. $ID3v2ShortFrameNameLookup[4]['involved_people_list'] = 'TIPL';
  2440. $ID3v2ShortFrameNameLookup[4]['musician_credits_list'] = 'TMCL';
  2441. $ID3v2ShortFrameNameLookup[4]['mood'] = 'TMOO';
  2442. $ID3v2ShortFrameNameLookup[4]['produced_notice'] = 'TPRO';
  2443. $ID3v2ShortFrameNameLookup[4]['album_sort_order'] = 'TSOA';
  2444. $ID3v2ShortFrameNameLookup[4]['performer_sort_order'] = 'TSOP';
  2445. $ID3v2ShortFrameNameLookup[4]['title_sort_order'] = 'TSOT';
  2446. $ID3v2ShortFrameNameLookup[4]['set_subtitle'] = 'TSST';
  2447. }
  2448. return @$ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)];
  2449. }
  2450. }
  2451. ?>