1<?php 2 3///////////////////////////////////////////////////////////////// 4/// getID3() by James Heinrich <info@getid3.org> // 5// available at https://github.com/JamesHeinrich/getID3 // 6// or https://www.getid3.org // 7// or http://getid3.sourceforge.net // 8// see readme.txt for more details // 9///////////////////////////////////////////////////////////////// 10/// // 11// write.php // 12// module for writing tags (APEv2, ID3v1, ID3v2) // 13// dependencies: getid3.lib.php // 14// write.apetag.php (optional) // 15// write.id3v1.php (optional) // 16// write.id3v2.php (optional) // 17// write.vorbiscomment.php (optional) // 18// write.metaflac.php (optional) // 19// write.lyrics3.php (optional) // 20// /// 21///////////////////////////////////////////////////////////////// 22 23if (!defined('GETID3_INCLUDEPATH')) { 24 throw new Exception('getid3.php MUST be included before calling getid3_writetags'); 25} 26if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { 27 throw new Exception('write.php depends on getid3.lib.php, which is missing.'); 28} 29 30/** 31 * NOTES: 32 * 33 * You should pass data here with standard field names as follows: 34 * * TITLE 35 * * ARTIST 36 * * ALBUM 37 * * TRACKNUMBER 38 * * COMMENT 39 * * GENRE 40 * * YEAR 41 * * ATTACHED_PICTURE (ID3v2 only) 42 * The APEv2 Tag Items Keys definition says "TRACK" is correct but foobar2000 uses "TRACKNUMBER" instead 43 * Pass data here as "TRACKNUMBER" for compatability with all formats 44 * 45 * @link http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html 46 */ 47class getid3_writetags 48{ 49 /** 50 * Absolute filename of file to write tags to. 51 * 52 * @var string 53 */ 54 public $filename; 55 56 /** 57 * Array of tag formats to write ('id3v1', 'id3v2.2', 'id2v2.3', 'id3v2.4', 'ape', 'vorbiscomment', 58 * 'metaflac', 'real'). 59 * 60 * @var array 61 */ 62 public $tagformats = array(); 63 64 /** 65 * 2-dimensional array of tag data (ex: $data['ARTIST'][0] = 'Elvis'). 66 * 67 * @var array 68 */ 69 public $tag_data = array(array()); 70 71 /** 72 * Text encoding used for tag data ('ISO-8859-1', 'UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE', ). 73 * 74 * @var string 75 */ 76 public $tag_encoding = 'ISO-8859-1'; 77 78 /** 79 * If true will erase existing tag data and write only passed data; if false will merge passed data 80 * with existing tag data. 81 * 82 * @var bool 83 */ 84 public $overwrite_tags = true; 85 86 /** 87 * If true will erase remove all existing tags and only write those passed in $tagformats; 88 * If false will ignore any tags not mentioned in $tagformats. 89 * 90 * @var bool 91 */ 92 public $remove_other_tags = false; 93 94 /** 95 * ISO-639-2 3-character language code needed for some ID3v2 frames. 96 * 97 * @link http://www.id3.org/iso639-2.html 98 * 99 * @var string 100 */ 101 public $id3v2_tag_language = 'eng'; 102 103 /** 104 * Minimum length of ID3v2 tags (will be padded to this length if tag data is shorter). 105 * 106 * @var int 107 */ 108 public $id3v2_paddedlength = 4096; 109 110 /** 111 * Any non-critical errors will be stored here. 112 * 113 * @var array 114 */ 115 public $warnings = array(); 116 117 /** 118 * Any critical errors will be stored here. 119 * 120 * @var array 121 */ 122 public $errors = array(); 123 124 /** 125 * Analysis of file before writing. 126 * 127 * @var array 128 */ 129 private $ThisFileInfo; 130 131 public function __construct() { 132 } 133 134 /** 135 * @return bool 136 */ 137 public function WriteTags() { 138 139 if (empty($this->filename)) { 140 $this->errors[] = 'filename is undefined in getid3_writetags'; 141 return false; 142 } elseif (!file_exists($this->filename)) { 143 $this->errors[] = 'filename set to non-existant file "'.$this->filename.'" in getid3_writetags'; 144 return false; 145 } 146 147 if (!is_array($this->tagformats)) { 148 $this->errors[] = 'tagformats must be an array in getid3_writetags'; 149 return false; 150 } 151 // prevent duplicate tag formats 152 $this->tagformats = array_unique($this->tagformats); 153 154 // prevent trying to specify more than one version of ID3v2 tag to write simultaneously 155 $id3typecounter = 0; 156 foreach ($this->tagformats as $tagformat) { 157 if (substr(strtolower($tagformat), 0, 6) == 'id3v2.') { 158 $id3typecounter++; 159 } 160 } 161 if ($id3typecounter > 1) { 162 $this->errors[] = 'tagformats must not contain more than one version of ID3v2'; 163 return false; 164 } 165 166 $TagFormatsToRemove = array(); 167 $AllowedTagFormats = array(); 168 if (filesize($this->filename) == 0) { 169 170 // empty file special case - allow any tag format, don't check existing format 171 // could be useful if you want to generate tag data for a non-existant file 172 $this->ThisFileInfo = array('fileformat'=>''); 173 $AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3'); 174 175 } else { 176 177 $getID3 = new getID3; 178 $getID3->encoding = $this->tag_encoding; 179 $this->ThisFileInfo = $getID3->analyze($this->filename); 180 181 // check for what file types are allowed on this fileformat 182 switch (isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : '') { 183 case 'mp3': 184 case 'mp2': 185 case 'mp1': 186 case 'riff': // maybe not officially, but people do it anyway 187 $AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3'); 188 break; 189 190 case 'mpc': 191 $AllowedTagFormats = array('ape'); 192 break; 193 194 case 'flac': 195 $AllowedTagFormats = array('metaflac'); 196 break; 197 198 case 'real': 199 $AllowedTagFormats = array('real'); 200 break; 201 202 case 'ogg': 203 switch (isset($this->ThisFileInfo['audio']['dataformat']) ? $this->ThisFileInfo['audio']['dataformat'] : '') { 204 case 'flac': 205 //$AllowedTagFormats = array('metaflac'); 206 $this->errors[] = 'metaflac is not (yet) compatible with OggFLAC files'; 207 return false; 208 case 'vorbis': 209 $AllowedTagFormats = array('vorbiscomment'); 210 break; 211 default: 212 $this->errors[] = 'metaflac is not (yet) compatible with Ogg files other than OggVorbis'; 213 return false; 214 } 215 break; 216 217 default: 218 $AllowedTagFormats = array(); 219 break; 220 } 221 foreach ($this->tagformats as $requested_tag_format) { 222 if (!in_array($requested_tag_format, $AllowedTagFormats)) { 223 $errormessage = 'Tag format "'.$requested_tag_format.'" is not allowed on "'.(isset($this->ThisFileInfo['fileformat']) ? $this->ThisFileInfo['fileformat'] : ''); 224 $errormessage .= (isset($this->ThisFileInfo['audio']['dataformat']) ? '.'.$this->ThisFileInfo['audio']['dataformat'] : ''); 225 $errormessage .= '" files'; 226 $this->errors[] = $errormessage; 227 return false; 228 } 229 } 230 231 // List of other tag formats, removed if requested 232 if ($this->remove_other_tags) { 233 foreach ($AllowedTagFormats as $AllowedTagFormat) { 234 switch ($AllowedTagFormat) { 235 case 'id3v2.2': 236 case 'id3v2.3': 237 case 'id3v2.4': 238 if (!in_array('id3v2', $TagFormatsToRemove) && !in_array('id3v2.2', $this->tagformats) && !in_array('id3v2.3', $this->tagformats) && !in_array('id3v2.4', $this->tagformats)) { 239 $TagFormatsToRemove[] = 'id3v2'; 240 } 241 break; 242 243 default: 244 if (!in_array($AllowedTagFormat, $this->tagformats)) { 245 $TagFormatsToRemove[] = $AllowedTagFormat; 246 } 247 break; 248 } 249 } 250 } 251 } 252 253 $WritingFilesToInclude = array_merge($this->tagformats, $TagFormatsToRemove); 254 255 // Check for required include files and include them 256 foreach ($WritingFilesToInclude as $tagformat) { 257 switch ($tagformat) { 258 case 'ape': 259 $GETID3_ERRORARRAY = &$this->errors; 260 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.apetag.php', __FILE__, true); 261 break; 262 263 case 'id3v1': 264 case 'lyrics3': 265 case 'vorbiscomment': 266 case 'metaflac': 267 case 'real': 268 $GETID3_ERRORARRAY = &$this->errors; 269 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.'.$tagformat.'.php', __FILE__, true); 270 break; 271 272 case 'id3v2.2': 273 case 'id3v2.3': 274 case 'id3v2.4': 275 case 'id3v2': 276 $GETID3_ERRORARRAY = &$this->errors; 277 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.id3v2.php', __FILE__, true); 278 break; 279 280 default: 281 $this->errors[] = 'unknown tag format "'.$tagformat.'" in $tagformats in WriteTags()'; 282 return false; 283 } 284 285 } 286 287 // Validation of supplied data 288 if (!is_array($this->tag_data)) { 289 $this->errors[] = '$this->tag_data is not an array in WriteTags()'; 290 return false; 291 } 292 // convert supplied data array keys to upper case, if they're not already 293 foreach ($this->tag_data as $tag_key => $tag_array) { 294 if (strtoupper($tag_key) !== $tag_key) { 295 $this->tag_data[strtoupper($tag_key)] = $this->tag_data[$tag_key]; 296 unset($this->tag_data[$tag_key]); 297 } 298 } 299 // convert source data array keys to upper case, if they're not already 300 if (!empty($this->ThisFileInfo['tags'])) { 301 foreach ($this->ThisFileInfo['tags'] as $tag_format => $tag_data_array) { 302 foreach ($tag_data_array as $tag_key => $tag_array) { 303 if (strtoupper($tag_key) !== $tag_key) { 304 $this->ThisFileInfo['tags'][$tag_format][strtoupper($tag_key)] = $this->ThisFileInfo['tags'][$tag_format][$tag_key]; 305 unset($this->ThisFileInfo['tags'][$tag_format][$tag_key]); 306 } 307 } 308 } 309 } 310 311 // Convert "TRACK" to "TRACK_NUMBER" (if needed) for compatability with all formats 312 if (isset($this->tag_data['TRACK']) && !isset($this->tag_data['TRACK_NUMBER'])) { 313 $this->tag_data['TRACK_NUMBER'] = $this->tag_data['TRACK']; 314 unset($this->tag_data['TRACK']); 315 } 316 317 // Remove all other tag formats, if requested 318 if ($this->remove_other_tags) { 319 $this->DeleteTags($TagFormatsToRemove); 320 } 321 322 // Write data for each tag format 323 foreach ($this->tagformats as $tagformat) { 324 $success = false; // overridden if tag writing is successful 325 switch ($tagformat) { 326 case 'ape': 327 $ape_writer = new getid3_write_apetag; 328 if ($ape_writer->tag_data = $this->FormatDataForAPE()) { 329 $ape_writer->filename = $this->filename; 330 if (($success = $ape_writer->WriteAPEtag()) === false) { 331 $this->errors[] = 'WriteAPEtag() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $ape_writer->errors)))).'</li></ul></pre>'; 332 } 333 } else { 334 $this->errors[] = 'FormatDataForAPE() failed'; 335 } 336 break; 337 338 case 'id3v1': 339 $id3v1_writer = new getid3_write_id3v1; 340 if ($id3v1_writer->tag_data = $this->FormatDataForID3v1()) { 341 $id3v1_writer->filename = $this->filename; 342 if (($success = $id3v1_writer->WriteID3v1()) === false) { 343 $this->errors[] = 'WriteID3v1() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $id3v1_writer->errors)))).'</li></ul></pre>'; 344 } 345 } else { 346 $this->errors[] = 'FormatDataForID3v1() failed'; 347 } 348 break; 349 350 case 'id3v2.2': 351 case 'id3v2.3': 352 case 'id3v2.4': 353 $id3v2_writer = new getid3_write_id3v2; 354 $id3v2_writer->majorversion = intval(substr($tagformat, -1)); 355 $id3v2_writer->paddedlength = $this->id3v2_paddedlength; 356 $id3v2_writer_tag_data = $this->FormatDataForID3v2($id3v2_writer->majorversion); 357 if ($id3v2_writer_tag_data !== false) { 358 $id3v2_writer->tag_data = $id3v2_writer_tag_data; 359 unset($id3v2_writer_tag_data); 360 $id3v2_writer->filename = $this->filename; 361 if (($success = $id3v2_writer->WriteID3v2()) === false) { 362 $this->errors[] = 'WriteID3v2() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $id3v2_writer->errors)))).'</li></ul></pre>'; 363 } 364 } else { 365 $this->errors[] = 'FormatDataForID3v2() failed'; 366 } 367 break; 368 369 case 'vorbiscomment': 370 $vorbiscomment_writer = new getid3_write_vorbiscomment; 371 if ($vorbiscomment_writer->tag_data = $this->FormatDataForVorbisComment()) { 372 $vorbiscomment_writer->filename = $this->filename; 373 if (($success = $vorbiscomment_writer->WriteVorbisComment()) === false) { 374 $this->errors[] = 'WriteVorbisComment() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $vorbiscomment_writer->errors)))).'</li></ul></pre>'; 375 } 376 } else { 377 $this->errors[] = 'FormatDataForVorbisComment() failed'; 378 } 379 break; 380 381 case 'metaflac': 382 $metaflac_writer = new getid3_write_metaflac; 383 if ($metaflac_writer->tag_data = $this->FormatDataForMetaFLAC()) { 384 $metaflac_writer->filename = $this->filename; 385 if (($success = $metaflac_writer->WriteMetaFLAC()) === false) { 386 $this->errors[] = 'WriteMetaFLAC() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $metaflac_writer->errors)))).'</li></ul></pre>'; 387 } 388 } else { 389 $this->errors[] = 'FormatDataForMetaFLAC() failed'; 390 } 391 break; 392 393 case 'real': 394 $real_writer = new getid3_write_real; 395 if ($real_writer->tag_data = $this->FormatDataForReal()) { 396 $real_writer->filename = $this->filename; 397 if (($success = $real_writer->WriteReal()) === false) { 398 $this->errors[] = 'WriteReal() failed with message(s):<pre><ul><li>'.str_replace("\n", '</li><li>', htmlentities(trim(implode("\n", $real_writer->errors)))).'</li></ul></pre>'; 399 } 400 } else { 401 $this->errors[] = 'FormatDataForReal() failed'; 402 } 403 break; 404 405 default: 406 $this->errors[] = 'Invalid tag format to write: "'.$tagformat.'"'; 407 return false; 408 } 409 if (!$success) { 410 return false; 411 } 412 } 413 return true; 414 415 } 416 417 /** 418 * @param string[] $TagFormatsToDelete 419 * 420 * @return bool 421 */ 422 public function DeleteTags($TagFormatsToDelete) { 423 foreach ($TagFormatsToDelete as $DeleteTagFormat) { 424 $success = false; // overridden if tag deletion is successful 425 switch ($DeleteTagFormat) { 426 case 'id3v1': 427 $id3v1_writer = new getid3_write_id3v1; 428 $id3v1_writer->filename = $this->filename; 429 if (($success = $id3v1_writer->RemoveID3v1()) === false) { 430 $this->errors[] = 'RemoveID3v1() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v1_writer->errors)).'</LI></UL></PRE>'; 431 } 432 break; 433 434 case 'id3v2': 435 $id3v2_writer = new getid3_write_id3v2; 436 $id3v2_writer->filename = $this->filename; 437 if (($success = $id3v2_writer->RemoveID3v2()) === false) { 438 $this->errors[] = 'RemoveID3v2() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v2_writer->errors)).'</LI></UL></PRE>'; 439 } 440 break; 441 442 case 'ape': 443 $ape_writer = new getid3_write_apetag; 444 $ape_writer->filename = $this->filename; 445 if (($success = $ape_writer->DeleteAPEtag()) === false) { 446 $this->errors[] = 'DeleteAPEtag() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $ape_writer->errors)).'</LI></UL></PRE>'; 447 } 448 break; 449 450 case 'vorbiscomment': 451 $vorbiscomment_writer = new getid3_write_vorbiscomment; 452 $vorbiscomment_writer->filename = $this->filename; 453 if (($success = $vorbiscomment_writer->DeleteVorbisComment()) === false) { 454 $this->errors[] = 'DeleteVorbisComment() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $vorbiscomment_writer->errors)).'</LI></UL></PRE>'; 455 } 456 break; 457 458 case 'metaflac': 459 $metaflac_writer = new getid3_write_metaflac; 460 $metaflac_writer->filename = $this->filename; 461 if (($success = $metaflac_writer->DeleteMetaFLAC()) === false) { 462 $this->errors[] = 'DeleteMetaFLAC() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $metaflac_writer->errors)).'</LI></UL></PRE>'; 463 } 464 break; 465 466 case 'lyrics3': 467 $lyrics3_writer = new getid3_write_lyrics3; 468 $lyrics3_writer->filename = $this->filename; 469 if (($success = $lyrics3_writer->DeleteLyrics3()) === false) { 470 $this->errors[] = 'DeleteLyrics3() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $lyrics3_writer->errors)).'</LI></UL></PRE>'; 471 } 472 break; 473 474 case 'real': 475 $real_writer = new getid3_write_real; 476 $real_writer->filename = $this->filename; 477 if (($success = $real_writer->RemoveReal()) === false) { 478 $this->errors[] = 'RemoveReal() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $real_writer->errors)).'</LI></UL></PRE>'; 479 } 480 break; 481 482 default: 483 $this->errors[] = 'Invalid tag format to delete: "'.$DeleteTagFormat.'"'; 484 return false; 485 } 486 if (!$success) { 487 return false; 488 } 489 } 490 return true; 491 } 492 493 /** 494 * @param string $TagFormat 495 * @param array $tag_data 496 * 497 * @return bool 498 * @throws Exception 499 */ 500 public function MergeExistingTagData($TagFormat, &$tag_data) { 501 // Merge supplied data with existing data, if requested 502 if ($this->overwrite_tags) { 503 // do nothing - ignore previous data 504 } else { 505 throw new Exception('$this->overwrite_tags=false is known to be buggy in this version of getID3. Check http://github.com/JamesHeinrich/getID3 for a newer version.'); 506// if (!isset($this->ThisFileInfo['tags'][$TagFormat])) { 507// return false; 508// } 509// $tag_data = array_merge_recursive($tag_data, $this->ThisFileInfo['tags'][$TagFormat]); 510 } 511 return true; 512 } 513 514 /** 515 * @return array 516 */ 517 public function FormatDataForAPE() { 518 $ape_tag_data = array(); 519 foreach ($this->tag_data as $tag_key => $valuearray) { 520 switch ($tag_key) { 521 case 'ATTACHED_PICTURE': 522 // ATTACHED_PICTURE is ID3v2 only - ignore 523 $this->warnings[] = '$data['.$tag_key.'] is assumed to be ID3v2 APIC data - NOT written to APE tag'; 524 break; 525 526 default: 527 foreach ($valuearray as $key => $value) { 528 if (is_string($value) || is_numeric($value)) { 529 $ape_tag_data[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value); 530 } else { 531 $this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to APE tag'; 532 unset($ape_tag_data[$tag_key]); 533 break; 534 } 535 } 536 break; 537 } 538 } 539 $this->MergeExistingTagData('ape', $ape_tag_data); 540 return $ape_tag_data; 541 } 542 543 /** 544 * @return array 545 */ 546 public function FormatDataForID3v1() { 547 $tag_data_id3v1 = array(); 548 $tag_data_id3v1['genreid'] = 255; 549 if (!empty($this->tag_data['GENRE'])) { 550 foreach ($this->tag_data['GENRE'] as $key => $value) { 551 if (getid3_id3v1::LookupGenreID($value) !== false) { 552 $tag_data_id3v1['genreid'] = getid3_id3v1::LookupGenreID($value); 553 break; 554 } 555 } 556 } 557 $tag_data_id3v1['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE'] ) ? $this->tag_data['TITLE'] : array()))); 558 $tag_data_id3v1['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST'] ) ? $this->tag_data['ARTIST'] : array()))); 559 $tag_data_id3v1['album'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ALBUM'] ) ? $this->tag_data['ALBUM'] : array()))); 560 $tag_data_id3v1['year'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['YEAR'] ) ? $this->tag_data['YEAR'] : array()))); 561 $tag_data_id3v1['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COMMENT'] ) ? $this->tag_data['COMMENT'] : array()))); 562 $tag_data_id3v1['track_number'] = intval(getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TRACK_NUMBER']) ? $this->tag_data['TRACK_NUMBER'] : array())))); 563 if ($tag_data_id3v1['track_number'] <= 0) { 564 $tag_data_id3v1['track_number'] = ''; 565 } 566 567 $this->MergeExistingTagData('id3v1', $tag_data_id3v1); 568 return $tag_data_id3v1; 569 } 570 571 /** 572 * @param int $id3v2_majorversion 573 * 574 * @return array|false 575 */ 576 public function FormatDataForID3v2($id3v2_majorversion) { 577 $tag_data_id3v2 = array(); 578 579 $ID3v2_text_encoding_lookup[2] = array('ISO-8859-1'=>0, 'UTF-16'=>1); 580 $ID3v2_text_encoding_lookup[3] = array('ISO-8859-1'=>0, 'UTF-16'=>1); 581 $ID3v2_text_encoding_lookup[4] = array('ISO-8859-1'=>0, 'UTF-16'=>1, 'UTF-16BE'=>2, 'UTF-8'=>3); 582 foreach ($this->tag_data as $tag_key => $valuearray) { 583 $ID3v2_framename = getid3_write_id3v2::ID3v2ShortFrameNameLookup($id3v2_majorversion, $tag_key); 584 switch ($ID3v2_framename) { 585 case 'APIC': 586 foreach ($valuearray as $key => $apic_data_array) { 587 if (isset($apic_data_array['data']) && 588 isset($apic_data_array['picturetypeid']) && 589 isset($apic_data_array['description']) && 590 isset($apic_data_array['mime'])) { 591 $tag_data_id3v2['APIC'][] = $apic_data_array; 592 } else { 593 $this->errors[] = 'ID3v2 APIC data is not properly structured'; 594 return false; 595 } 596 } 597 break; 598 599 case 'POPM': 600 if (isset($valuearray['email']) && 601 isset($valuearray['rating']) && 602 isset($valuearray['data'])) { 603 $tag_data_id3v2['POPM'][] = $valuearray; 604 } else { 605 $this->errors[] = 'ID3v2 POPM data is not properly structured'; 606 return false; 607 } 608 break; 609 610 case 'GRID': 611 if ( 612 isset($valuearray['groupsymbol']) && 613 isset($valuearray['ownerid']) && 614 isset($valuearray['data']) 615 ) { 616 $tag_data_id3v2['GRID'][] = $valuearray; 617 } else { 618 $this->errors[] = 'ID3v2 GRID data is not properly structured'; 619 return false; 620 } 621 break; 622 623 case 'UFID': 624 if (isset($valuearray['ownerid']) && 625 isset($valuearray['data'])) { 626 $tag_data_id3v2['UFID'][] = $valuearray; 627 } else { 628 $this->errors[] = 'ID3v2 UFID data is not properly structured'; 629 return false; 630 } 631 break; 632 633 case 'TXXX': 634 foreach ($valuearray as $key => $txxx_data_array) { 635 if (isset($txxx_data_array['description']) && isset($txxx_data_array['data'])) { 636 $tag_data_id3v2['TXXX'][] = $txxx_data_array; 637 } else { 638 $this->errors[] = 'ID3v2 TXXX data is not properly structured'; 639 return false; 640 } 641 } 642 break; 643 644 case '': 645 $this->errors[] = 'ID3v2: Skipping "'.$tag_key.'" because cannot match it to a known ID3v2 frame type'; 646 // some other data type, don't know how to handle it, ignore it 647 break; 648 649 default: 650 // most other (text) frames can be copied over as-is 651 foreach ($valuearray as $key => $value) { 652 if (isset($ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding])) { 653 // source encoding is valid in ID3v2 - use it with no conversion 654 $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = $ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding]; 655 $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value; 656 } else { 657 // source encoding is NOT valid in ID3v2 - convert it to an ID3v2-valid encoding first 658 if ($id3v2_majorversion < 4) { 659 // convert data from other encoding to UTF-16 (with BOM) 660 // note: some software, notably Windows Media Player and iTunes are broken and treat files tagged with UTF-16BE (with BOM) as corrupt 661 // therefore we force data to UTF-16LE and manually prepend the BOM 662 $ID3v2_tag_data_converted = false; 663 if (/*!$ID3v2_tag_data_converted && */($this->tag_encoding == 'ISO-8859-1')) { 664 // great, leave data as-is for minimum compatability problems 665 $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0; 666 $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value; 667 $ID3v2_tag_data_converted = true; 668 } 669 if (!$ID3v2_tag_data_converted && ($this->tag_encoding == 'UTF-8')) { 670 do { 671 // if UTF-8 string does not include any characters above chr(127) then it is identical to ISO-8859-1 672 for ($i = 0; $i < strlen($value); $i++) { 673 if (ord($value[$i]) > 127) { 674 break 2; 675 } 676 } 677 $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 0; 678 $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value; 679 $ID3v2_tag_data_converted = true; 680 } while (false); 681 } 682 if (!$ID3v2_tag_data_converted) { 683 $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 1; 684 //$tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16', $value); // output is UTF-16LE+BOM or UTF-16BE+BOM depending on system architecture 685 $tag_data_id3v2[$ID3v2_framename][$key]['data'] = "\xFF\xFE".getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16LE', $value); // force LittleEndian order version of UTF-16 686 $ID3v2_tag_data_converted = true; 687 } 688 689 } else { 690 // convert data from other encoding to UTF-8 691 $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 3; 692 $tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value); 693 } 694 } 695 696 // These values are not needed for all frame types, but if they're not used no matter 697 $tag_data_id3v2[$ID3v2_framename][$key]['description'] = ''; 698 $tag_data_id3v2[$ID3v2_framename][$key]['language'] = $this->id3v2_tag_language; 699 } 700 break; 701 } 702 } 703 $this->MergeExistingTagData('id3v2', $tag_data_id3v2); 704 return $tag_data_id3v2; 705 } 706 707 /** 708 * @return array 709 */ 710 public function FormatDataForVorbisComment() { 711 $tag_data_vorbiscomment = $this->tag_data; 712 713 // check for multi-line comment values - split out to multiple comments if neccesary 714 // and convert data to UTF-8 strings 715 foreach ($tag_data_vorbiscomment as $tag_key => $valuearray) { 716 foreach ($valuearray as $key => $value) { 717 if (($tag_key == 'ATTACHED_PICTURE') && is_array($value)) { 718 continue; // handled separately in write.metaflac.php 719 } else { 720 str_replace("\r", "\n", $value); 721 if (strstr($value, "\n")) { 722 unset($tag_data_vorbiscomment[$tag_key][$key]); 723 $multilineexploded = explode("\n", $value); 724 foreach ($multilineexploded as $newcomment) { 725 if (strlen(trim($newcomment)) > 0) { 726 $tag_data_vorbiscomment[$tag_key][] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $newcomment); 727 } 728 } 729 } elseif (is_string($value) || is_numeric($value)) { 730 $tag_data_vorbiscomment[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value); 731 } else { 732 $this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to VorbisComment tag'; 733 unset($tag_data_vorbiscomment[$tag_key]); 734 break; 735 } 736 } 737 } 738 } 739 $this->MergeExistingTagData('vorbiscomment', $tag_data_vorbiscomment); 740 return $tag_data_vorbiscomment; 741 } 742 743 /** 744 * @return array 745 */ 746 public function FormatDataForMetaFLAC() { 747 // FLAC & OggFLAC use VorbisComments same as OggVorbis 748 // but require metaflac to do the writing rather than vorbiscomment 749 return $this->FormatDataForVorbisComment(); 750 } 751 752 /** 753 * @return array 754 */ 755 public function FormatDataForReal() { 756 $tag_data_real['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['TITLE'] ) ? $this->tag_data['TITLE'] : array()))); 757 $tag_data_real['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['ARTIST'] ) ? $this->tag_data['ARTIST'] : array()))); 758 $tag_data_real['copyright'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COPYRIGHT']) ? $this->tag_data['COPYRIGHT'] : array()))); 759 $tag_data_real['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', implode(' ', (isset($this->tag_data['COMMENT'] ) ? $this->tag_data['COMMENT'] : array()))); 760 761 $this->MergeExistingTagData('real', $tag_data_real); 762 return $tag_data_real; 763 } 764 765} 766