[SPIP] ~maj v3.2.9-->v3.2.11
[lhc/web/www.git] / www / plugins-dist / medias / lib / getid3 / write.php
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
23 if (!defined('GETID3_INCLUDEPATH')) {
24 throw new Exception('getid3.php MUST be included before calling getid3_writetags');
25 }
26 if (!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 */
47 class 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 }