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