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