PageRenderTime 60ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/getid3/write.id3v2.php

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