WordPress 4.3
[autoinstalls/wordpress.git] / wp-includes / ID3 / module.tag.id3v1.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 // module.tag.id3v1.php                                        //
12 // module for analyzing ID3v1 tags                             //
13 // dependencies: NONE                                          //
14 //                                                            ///
15 /////////////////////////////////////////////////////////////////
16
17
18 class getid3_id3v1 extends getid3_handler
19 {
20
21         public function Analyze() {
22                 $info = &$this->getid3->info;
23
24                 if (!getid3_lib::intValueSupported($info['filesize'])) {
25                         $info['warning'][] = 'Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
26                         return false;
27                 }
28
29                 $this->fseek(-256, SEEK_END);
30                 $preid3v1 = $this->fread(128);
31                 $id3v1tag = $this->fread(128);
32
33                 if (substr($id3v1tag, 0, 3) == 'TAG') {
34
35                         $info['avdataend'] = $info['filesize'] - 128;
36
37                         $ParsedID3v1['title']   = $this->cutfield(substr($id3v1tag,   3, 30));
38                         $ParsedID3v1['artist']  = $this->cutfield(substr($id3v1tag,  33, 30));
39                         $ParsedID3v1['album']   = $this->cutfield(substr($id3v1tag,  63, 30));
40                         $ParsedID3v1['year']    = $this->cutfield(substr($id3v1tag,  93,  4));
41                         $ParsedID3v1['comment'] =                 substr($id3v1tag,  97, 30);  // can't remove nulls yet, track detection depends on them
42                         $ParsedID3v1['genreid'] =             ord(substr($id3v1tag, 127,  1));
43
44                         // If second-last byte of comment field is null and last byte of comment field is non-null
45                         // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
46                         if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) {
47                                 $ParsedID3v1['track']   = ord(substr($ParsedID3v1['comment'], 29,  1));
48                                 $ParsedID3v1['comment'] =     substr($ParsedID3v1['comment'],  0, 28);
49                         }
50                         $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
51
52                         $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
53                         if (!empty($ParsedID3v1['genre'])) {
54                                 unset($ParsedID3v1['genreid']);
55                         }
56                         if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
57                                 unset($ParsedID3v1['genre']);
58                         }
59
60                         foreach ($ParsedID3v1 as $key => $value) {
61                                 $ParsedID3v1['comments'][$key][0] = $value;
62                         }
63
64                         // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
65                         $GoodFormatID3v1tag = $this->GenerateID3v1Tag(
66                                                                                         $ParsedID3v1['title'],
67                                                                                         $ParsedID3v1['artist'],
68                                                                                         $ParsedID3v1['album'],
69                                                                                         $ParsedID3v1['year'],
70                                                                                         (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
71                                                                                         $ParsedID3v1['comment'],
72                                                                                         (!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : ''));
73                         $ParsedID3v1['padding_valid'] = true;
74                         if ($id3v1tag !== $GoodFormatID3v1tag) {
75                                 $ParsedID3v1['padding_valid'] = false;
76                                 $info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding';
77                         }
78
79                         $ParsedID3v1['tag_offset_end']   = $info['filesize'];
80                         $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
81
82                         $info['id3v1'] = $ParsedID3v1;
83                 }
84
85                 if (substr($preid3v1, 0, 3) == 'TAG') {
86                         // The way iTunes handles tags is, well, brain-damaged.
87                         // It completely ignores v1 if ID3v2 is present.
88                         // This goes as far as adding a new v1 tag *even if there already is one*
89
90                         // A suspected double-ID3v1 tag has been detected, but it could be that
91                         // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
92                         if (substr($preid3v1, 96, 8) == 'APETAGEX') {
93                                 // an APE tag footer was found before the last ID3v1, assume false "TAG" synch
94                         } elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
95                                 // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
96                         } else {
97                                 // APE and Lyrics3 footers not found - assume double ID3v1
98                                 $info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes';
99                                 $info['avdataend'] -= 128;
100                         }
101                 }
102
103                 return true;
104         }
105
106         public static function cutfield($str) {
107                 return trim(substr($str, 0, strcspn($str, "\x00")));
108         }
109
110         public static function ArrayOfGenres($allowSCMPXextended=false) {
111                 static $GenreLookup = array(
112                         0    => 'Blues',
113                         1    => 'Classic Rock',
114                         2    => 'Country',
115                         3    => 'Dance',
116                         4    => 'Disco',
117                         5    => 'Funk',
118                         6    => 'Grunge',
119                         7    => 'Hip-Hop',
120                         8    => 'Jazz',
121                         9    => 'Metal',
122                         10   => 'New Age',
123                         11   => 'Oldies',
124                         12   => 'Other',
125                         13   => 'Pop',
126                         14   => 'R&B',
127                         15   => 'Rap',
128                         16   => 'Reggae',
129                         17   => 'Rock',
130                         18   => 'Techno',
131                         19   => 'Industrial',
132                         20   => 'Alternative',
133                         21   => 'Ska',
134                         22   => 'Death Metal',
135                         23   => 'Pranks',
136                         24   => 'Soundtrack',
137                         25   => 'Euro-Techno',
138                         26   => 'Ambient',
139                         27   => 'Trip-Hop',
140                         28   => 'Vocal',
141                         29   => 'Jazz+Funk',
142                         30   => 'Fusion',
143                         31   => 'Trance',
144                         32   => 'Classical',
145                         33   => 'Instrumental',
146                         34   => 'Acid',
147                         35   => 'House',
148                         36   => 'Game',
149                         37   => 'Sound Clip',
150                         38   => 'Gospel',
151                         39   => 'Noise',
152                         40   => 'Alt. Rock',
153                         41   => 'Bass',
154                         42   => 'Soul',
155                         43   => 'Punk',
156                         44   => 'Space',
157                         45   => 'Meditative',
158                         46   => 'Instrumental Pop',
159                         47   => 'Instrumental Rock',
160                         48   => 'Ethnic',
161                         49   => 'Gothic',
162                         50   => 'Darkwave',
163                         51   => 'Techno-Industrial',
164                         52   => 'Electronic',
165                         53   => 'Pop-Folk',
166                         54   => 'Eurodance',
167                         55   => 'Dream',
168                         56   => 'Southern Rock',
169                         57   => 'Comedy',
170                         58   => 'Cult',
171                         59   => 'Gangsta Rap',
172                         60   => 'Top 40',
173                         61   => 'Christian Rap',
174                         62   => 'Pop/Funk',
175                         63   => 'Jungle',
176                         64   => 'Native American',
177                         65   => 'Cabaret',
178                         66   => 'New Wave',
179                         67   => 'Psychedelic',
180                         68   => 'Rave',
181                         69   => 'Showtunes',
182                         70   => 'Trailer',
183                         71   => 'Lo-Fi',
184                         72   => 'Tribal',
185                         73   => 'Acid Punk',
186                         74   => 'Acid Jazz',
187                         75   => 'Polka',
188                         76   => 'Retro',
189                         77   => 'Musical',
190                         78   => 'Rock & Roll',
191                         79   => 'Hard Rock',
192                         80   => 'Folk',
193                         81   => 'Folk/Rock',
194                         82   => 'National Folk',
195                         83   => 'Swing',
196                         84   => 'Fast-Fusion',
197                         85   => 'Bebob',
198                         86   => 'Latin',
199                         87   => 'Revival',
200                         88   => 'Celtic',
201                         89   => 'Bluegrass',
202                         90   => 'Avantgarde',
203                         91   => 'Gothic Rock',
204                         92   => 'Progressive Rock',
205                         93   => 'Psychedelic Rock',
206                         94   => 'Symphonic Rock',
207                         95   => 'Slow Rock',
208                         96   => 'Big Band',
209                         97   => 'Chorus',
210                         98   => 'Easy Listening',
211                         99   => 'Acoustic',
212                         100  => 'Humour',
213                         101  => 'Speech',
214                         102  => 'Chanson',
215                         103  => 'Opera',
216                         104  => 'Chamber Music',
217                         105  => 'Sonata',
218                         106  => 'Symphony',
219                         107  => 'Booty Bass',
220                         108  => 'Primus',
221                         109  => 'Porn Groove',
222                         110  => 'Satire',
223                         111  => 'Slow Jam',
224                         112  => 'Club',
225                         113  => 'Tango',
226                         114  => 'Samba',
227                         115  => 'Folklore',
228                         116  => 'Ballad',
229                         117  => 'Power Ballad',
230                         118  => 'Rhythmic Soul',
231                         119  => 'Freestyle',
232                         120  => 'Duet',
233                         121  => 'Punk Rock',
234                         122  => 'Drum Solo',
235                         123  => 'A Cappella',
236                         124  => 'Euro-House',
237                         125  => 'Dance Hall',
238                         126  => 'Goa',
239                         127  => 'Drum & Bass',
240                         128  => 'Club-House',
241                         129  => 'Hardcore',
242                         130  => 'Terror',
243                         131  => 'Indie',
244                         132  => 'BritPop',
245                         133  => 'Negerpunk',
246                         134  => 'Polsk Punk',
247                         135  => 'Beat',
248                         136  => 'Christian Gangsta Rap',
249                         137  => 'Heavy Metal',
250                         138  => 'Black Metal',
251                         139  => 'Crossover',
252                         140  => 'Contemporary Christian',
253                         141  => 'Christian Rock',
254                         142  => 'Merengue',
255                         143  => 'Salsa',
256                         144  => 'Thrash Metal',
257                         145  => 'Anime',
258                         146  => 'JPop',
259                         147  => 'Synthpop',
260
261                         255  => 'Unknown',
262
263                         'CR' => 'Cover',
264                         'RX' => 'Remix'
265                 );
266
267                 static $GenreLookupSCMPX = array();
268                 if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
269                         $GenreLookupSCMPX = $GenreLookup;
270                         // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
271                         // Extended ID3v1 genres invented by SCMPX
272                         // Note that 255 "Japanese Anime" conflicts with standard "Unknown"
273                         $GenreLookupSCMPX[240] = 'Sacred';
274                         $GenreLookupSCMPX[241] = 'Northern Europe';
275                         $GenreLookupSCMPX[242] = 'Irish & Scottish';
276                         $GenreLookupSCMPX[243] = 'Scotland';
277                         $GenreLookupSCMPX[244] = 'Ethnic Europe';
278                         $GenreLookupSCMPX[245] = 'Enka';
279                         $GenreLookupSCMPX[246] = 'Children\'s Song';
280                         $GenreLookupSCMPX[247] = 'Japanese Sky';
281                         $GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
282                         $GenreLookupSCMPX[249] = 'Japanese Doom Rock';
283                         $GenreLookupSCMPX[250] = 'Japanese J-POP';
284                         $GenreLookupSCMPX[251] = 'Japanese Seiyu';
285                         $GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
286                         $GenreLookupSCMPX[253] = 'Japanese Moemoe';
287                         $GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
288                         //$GenreLookupSCMPX[255] = 'Japanese Anime';
289                 }
290
291                 return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
292         }
293
294         public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
295                 switch ($genreid) {
296                         case 'RX':
297                         case 'CR':
298                                 break;
299                         default:
300                                 if (!is_numeric($genreid)) {
301                                         return false;
302                                 }
303                                 $genreid = intval($genreid); // to handle 3 or '3' or '03'
304                                 break;
305                 }
306                 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
307                 return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
308         }
309
310         public static function LookupGenreID($genre, $allowSCMPXextended=false) {
311                 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
312                 $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
313                 foreach ($GenreLookup as $key => $value) {
314                         if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
315                                 return $key;
316                         }
317                 }
318                 return false;
319         }
320
321         public static function StandardiseID3v1GenreName($OriginalGenre) {
322                 if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
323                         return self::LookupGenreName($GenreID);
324                 }
325                 return $OriginalGenre;
326         }
327
328         public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
329                 $ID3v1Tag  = 'TAG';
330                 $ID3v1Tag .= str_pad(trim(substr($title,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
331                 $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
332                 $ID3v1Tag .= str_pad(trim(substr($album,  0, 30)), 30, "\x00", STR_PAD_RIGHT);
333                 $ID3v1Tag .= str_pad(trim(substr($year,   0,  4)),  4, "\x00", STR_PAD_LEFT);
334                 if (!empty($track) && ($track > 0) && ($track <= 255)) {
335                         $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
336                         $ID3v1Tag .= "\x00";
337                         if (gettype($track) == 'string') {
338                                 $track = (int) $track;
339                         }
340                         $ID3v1Tag .= chr($track);
341                 } else {
342                         $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
343                 }
344                 if (($genreid < 0) || ($genreid > 147)) {
345                         $genreid = 255; // 'unknown' genre
346                 }
347                 switch (gettype($genreid)) {
348                         case 'string':
349                         case 'integer':
350                                 $ID3v1Tag .= chr(intval($genreid));
351                                 break;
352                         default:
353                                 $ID3v1Tag .= chr(255); // 'unknown' genre
354                                 break;
355                 }
356
357                 return $ID3v1Tag;
358         }
359
360 }