run:R W Run
54.21 KB
2026-03-11 16:18:51
R W Run
79.05 KB
2026-03-11 16:18:51
R W Run
1.36 KB
2026-03-11 16:18:51
R W Run
133.61 KB
2026-03-11 16:18:51
R W Run
26.5 KB
2026-03-11 16:18:51
R W Run
104.64 KB
2026-03-11 16:18:51
R W Run
164.97 KB
2026-03-11 16:18:51
R W Run
136.53 KB
2026-03-11 16:18:51
R W Run
38.46 KB
2026-03-11 16:18:51
R W Run
10.63 KB
2026-03-11 16:18:51
R W Run
19.23 KB
2026-03-11 16:18:51
R W Run
104.5 KB
2026-03-11 16:18:51
R W Run
42.74 KB
2026-03-11 16:18:51
R W Run
18.63 KB
2026-03-11 16:18:51
R W Run
14.7 KB
2026-03-11 16:18:51
R W Run
151.2 KB
2026-03-11 16:18:51
R W Run
11.75 KB
2026-03-11 16:18:51
R W Run
25.71 KB
2026-03-11 16:18:51
R W Run
error_log
📄module.tag.id3v1.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// module.tag.id3v1.php //
12// module for analyzing ID3v1 tags //
13// dependencies: NONE //
14// ///
15/////////////////////////////////////////////////////////////////
16
17if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
18 exit;
19}
20
21class getid3_id3v1 extends getid3_handler
22{
23 /**
24 * @return bool
25 */
26 public function Analyze() {
27 $info = &$this->getid3->info;
28
29 if (!getid3_lib::intValueSupported($info['filesize'])) {
30 $this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
31 return false;
32 }
33
34 if($info['filesize'] < 256) {
35 $this->fseek(-128, SEEK_END);
36 $preid3v1 = '';
37 $id3v1tag = $this->fread(128);
38 } else {
39 $this->fseek(-256, SEEK_END);
40 $preid3v1 = $this->fread(128);
41 $id3v1tag = $this->fread(128);
42 }
43
44
45 if (substr($id3v1tag, 0, 3) == 'TAG') {
46
47 $info['avdataend'] = $info['filesize'] - 128;
48
49 $ParsedID3v1 = array();
50 $ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30));
51 $ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30));
52 $ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30));
53 $ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4));
54 $ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them
55 $ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1));
56
57 // If second-last byte of comment field is null and last byte of comment field is non-null
58 // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
59 if (($id3v1tag[125] === "\x00") && ($id3v1tag[126] !== "\x00")) {
60 $ParsedID3v1['track_number'] = ord(substr($ParsedID3v1['comment'], 29, 1));
61 $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28);
62 }
63 $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
64
65 $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
66 if (!empty($ParsedID3v1['genre'])) {
67 unset($ParsedID3v1['genreid']);
68 }
69 if (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown')) {
70 unset($ParsedID3v1['genre']);
71 }
72
73 foreach ($ParsedID3v1 as $key => $value) {
74 $ParsedID3v1['comments'][$key][0] = $value;
75 }
76 $ID3v1encoding = $this->getid3->encoding_id3v1;
77 if ($this->getid3->encoding_id3v1_autodetect) {
78 // ID3v1 encoding detection hack START
79 // ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets
80 // Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess
81 foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) {
82 foreach ($valuearray as $key => $value) {
83 if (preg_match('#^[\\x00-\\x40\\x80-\\xFF]+$#', $value) && !ctype_digit((string) $value)) { // check for strings with only characters above chr(128) and punctuation/numbers, but not just numeric strings (e.g. track numbers or years)
84 foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) {
85 if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) {
86 $ID3v1encoding = $id3v1_bad_encoding;
87 $this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key);
88 break 3;
89 } elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) {
90 $ID3v1encoding = $id3v1_bad_encoding;
91 $this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key);
92 break 3;
93 }
94 }
95 }
96 }
97 }
98 // ID3v1 encoding detection hack END
99 }
100
101 // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
102 $GoodFormatID3v1tag = $this->GenerateID3v1Tag(
103 $ParsedID3v1['title'],
104 $ParsedID3v1['artist'],
105 $ParsedID3v1['album'],
106 $ParsedID3v1['year'],
107 (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
108 $ParsedID3v1['comment'],
109 (!empty($ParsedID3v1['track_number']) ? $ParsedID3v1['track_number'] : ''));
110 $ParsedID3v1['padding_valid'] = true;
111 if ($id3v1tag !== $GoodFormatID3v1tag) {
112 $ParsedID3v1['padding_valid'] = false;
113 $this->warning('Some ID3v1 fields do not use NULL characters for padding');
114 }
115
116 $ParsedID3v1['tag_offset_end'] = $info['filesize'];
117 $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
118
119 $info['id3v1'] = $ParsedID3v1;
120 $info['id3v1']['encoding'] = $ID3v1encoding;
121 }
122
123 if (substr($preid3v1, 0, 3) == 'TAG') {
124 // The way iTunes handles tags is, well, brain-damaged.
125 // It completely ignores v1 if ID3v2 is present.
126 // This goes as far as adding a new v1 tag *even if there already is one*
127
128 // A suspected double-ID3v1 tag has been detected, but it could be that
129 // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
130 if (substr($preid3v1, 96, 8) == 'APETAGEX') {
131 // an APE tag footer was found before the last ID3v1, assume false "TAG" synch
132 } elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
133 // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
134 } else {
135 // APE and Lyrics3 footers not found - assume double ID3v1
136 $this->warning('Duplicate ID3v1 tag detected - this has been known to happen with iTunes');
137 $info['avdataend'] -= 128;
138 }
139 }
140
141 return true;
142 }
143
144 /**
145 * @param string $str
146 *
147 * @return string
148 */
149 public static function cutfield($str) {
150 return trim(substr($str, 0, strcspn($str, "\x00")));
151 }
152
153 /**
154 * @param bool $allowSCMPXextended
155 *
156 * @return string[]
157 */
158 public static function ArrayOfGenres($allowSCMPXextended=false) {
159 static $GenreLookup = array(
160 0 => 'Blues',
161 1 => 'Classic Rock',
162 2 => 'Country',
163 3 => 'Dance',
164 4 => 'Disco',
165 5 => 'Funk',
166 6 => 'Grunge',
167 7 => 'Hip-Hop',
168 8 => 'Jazz',
169 9 => 'Metal',
170 10 => 'New Age',
171 11 => 'Oldies',
172 12 => 'Other',
173 13 => 'Pop',
174 14 => 'R&B',
175 15 => 'Rap',
176 16 => 'Reggae',
177 17 => 'Rock',
178 18 => 'Techno',
179 19 => 'Industrial',
180 20 => 'Alternative',
181 21 => 'Ska',
182 22 => 'Death Metal',
183 23 => 'Pranks',
184 24 => 'Soundtrack',
185 25 => 'Euro-Techno',
186 26 => 'Ambient',
187 27 => 'Trip-Hop',
188 28 => 'Vocal',
189 29 => 'Jazz+Funk',
190 30 => 'Fusion',
191 31 => 'Trance',
192 32 => 'Classical',
193 33 => 'Instrumental',
194 34 => 'Acid',
195 35 => 'House',
196 36 => 'Game',
197 37 => 'Sound Clip',
198 38 => 'Gospel',
199 39 => 'Noise',
200 40 => 'Alt. Rock',
201 41 => 'Bass',
202 42 => 'Soul',
203 43 => 'Punk',
204 44 => 'Space',
205 45 => 'Meditative',
206 46 => 'Instrumental Pop',
207 47 => 'Instrumental Rock',
208 48 => 'Ethnic',
209 49 => 'Gothic',
210 50 => 'Darkwave',
211 51 => 'Techno-Industrial',
212 52 => 'Electronic',
213 53 => 'Pop-Folk',
214 54 => 'Eurodance',
215 55 => 'Dream',
216 56 => 'Southern Rock',
217 57 => 'Comedy',
218 58 => 'Cult',
219 59 => 'Gangsta Rap',
220 60 => 'Top 40',
221 61 => 'Christian Rap',
222 62 => 'Pop/Funk',
223 63 => 'Jungle',
224 64 => 'Native American',
225 65 => 'Cabaret',
226 66 => 'New Wave',
227 67 => 'Psychedelic',
228 68 => 'Rave',
229 69 => 'Showtunes',
230 70 => 'Trailer',
231 71 => 'Lo-Fi',
232 72 => 'Tribal',
233 73 => 'Acid Punk',
234 74 => 'Acid Jazz',
235 75 => 'Polka',
236 76 => 'Retro',
237 77 => 'Musical',
238 78 => 'Rock & Roll',
239 79 => 'Hard Rock',
240 80 => 'Folk',
241 81 => 'Folk/Rock',
242 82 => 'National Folk',
243 83 => 'Swing',
244 84 => 'Fast-Fusion',
245 85 => 'Bebob',
246 86 => 'Latin',
247 87 => 'Revival',
248 88 => 'Celtic',
249 89 => 'Bluegrass',
250 90 => 'Avantgarde',
251 91 => 'Gothic Rock',
252 92 => 'Progressive Rock',
253 93 => 'Psychedelic Rock',
254 94 => 'Symphonic Rock',
255 95 => 'Slow Rock',
256 96 => 'Big Band',
257 97 => 'Chorus',
258 98 => 'Easy Listening',
259 99 => 'Acoustic',
260 100 => 'Humour',
261 101 => 'Speech',
262 102 => 'Chanson',
263 103 => 'Opera',
264 104 => 'Chamber Music',
265 105 => 'Sonata',
266 106 => 'Symphony',
267 107 => 'Booty Bass',
268 108 => 'Primus',
269 109 => 'Porn Groove',
270 110 => 'Satire',
271 111 => 'Slow Jam',
272 112 => 'Club',
273 113 => 'Tango',
274 114 => 'Samba',
275 115 => 'Folklore',
276 116 => 'Ballad',
277 117 => 'Power Ballad',
278 118 => 'Rhythmic Soul',
279 119 => 'Freestyle',
280 120 => 'Duet',
281 121 => 'Punk Rock',
282 122 => 'Drum Solo',
283 123 => 'A Cappella',
284 124 => 'Euro-House',
285 125 => 'Dance Hall',
286 126 => 'Goa',
287 127 => 'Drum & Bass',
288 128 => 'Club-House',
289 129 => 'Hardcore',
290 130 => 'Terror',
291 131 => 'Indie',
292 132 => 'BritPop',
293 133 => 'Negerpunk',
294 134 => 'Polsk Punk',
295 135 => 'Beat',
296 136 => 'Christian Gangsta Rap',
297 137 => 'Heavy Metal',
298 138 => 'Black Metal',
299 139 => 'Crossover',
300 140 => 'Contemporary Christian',
301 141 => 'Christian Rock',
302 142 => 'Merengue',
303 143 => 'Salsa',
304 144 => 'Thrash Metal',
305 145 => 'Anime',
306 146 => 'JPop',
307 147 => 'Synthpop',
308 148 => 'Abstract',
309 149 => 'Art Rock',
310 150 => 'Baroque',
311 151 => 'Bhangra',
312 152 => 'Big Beat',
313 153 => 'Breakbeat',
314 154 => 'Chillout',
315 155 => 'Downtempo',
316 156 => 'Dub',
317 157 => 'EBM',
318 158 => 'Eclectic',
319 159 => 'Electro',
320 160 => 'Electroclash',
321 161 => 'Emo',
322 162 => 'Experimental',
323 163 => 'Garage',
324 164 => 'Global',
325 165 => 'IDM',
326 166 => 'Illbient',
327 167 => 'Industro-Goth',
328 168 => 'Jam Band',
329 169 => 'Krautrock',
330 170 => 'Leftfield',
331 171 => 'Lounge',
332 172 => 'Math Rock',
333 173 => 'New Romantic',
334 174 => 'Nu-Breakz',
335 175 => 'Post-Punk',
336 176 => 'Post-Rock',
337 177 => 'Psytrance',
338 178 => 'Shoegaze',
339 179 => 'Space Rock',
340 180 => 'Trop Rock',
341 181 => 'World Music',
342 182 => 'Neoclassical',
343 183 => 'Audiobook',
344 184 => 'Audio Theatre',
345 185 => 'Neue Deutsche Welle',
346 186 => 'Podcast',
347 187 => 'Indie-Rock',
348 188 => 'G-Funk',
349 189 => 'Dubstep',
350 190 => 'Garage Rock',
351 191 => 'Psybient',
352
353 255 => 'Unknown',
354
355 'CR' => 'Cover',
356 'RX' => 'Remix'
357 );
358
359 static $GenreLookupSCMPX = array();
360 if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
361 $GenreLookupSCMPX = $GenreLookup;
362 // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
363 // Extended ID3v1 genres invented by SCMPX
364 // Note that 255 "Japanese Anime" conflicts with standard "Unknown"
365 $GenreLookupSCMPX[240] = 'Sacred';
366 $GenreLookupSCMPX[241] = 'Northern Europe';
367 $GenreLookupSCMPX[242] = 'Irish & Scottish';
368 $GenreLookupSCMPX[243] = 'Scotland';
369 $GenreLookupSCMPX[244] = 'Ethnic Europe';
370 $GenreLookupSCMPX[245] = 'Enka';
371 $GenreLookupSCMPX[246] = 'Children\'s Song';
372 $GenreLookupSCMPX[247] = 'Japanese Sky';
373 $GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
374 $GenreLookupSCMPX[249] = 'Japanese Doom Rock';
375 $GenreLookupSCMPX[250] = 'Japanese J-POP';
376 $GenreLookupSCMPX[251] = 'Japanese Seiyu';
377 $GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
378 $GenreLookupSCMPX[253] = 'Japanese Moemoe';
379 $GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
380 //$GenreLookupSCMPX[255] = 'Japanese Anime';
381 }
382
383 return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
384 }
385
386 /**
387 * @param string $genreid
388 * @param bool $allowSCMPXextended
389 *
390 * @return string|false
391 */
392 public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
393 switch ($genreid) {
394 case 'RX':
395 case 'CR':
396 break;
397 default:
398 if (!is_numeric($genreid)) {
399 return false;
400 }
401 $genreid = intval($genreid); // to handle 3 or '3' or '03'
402 break;
403 }
404 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
405 return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
406 }
407
408 /**
409 * @param string $genre
410 * @param bool $allowSCMPXextended
411 *
412 * @return string|false
413 */
414 public static function LookupGenreID($genre, $allowSCMPXextended=false) {
415 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
416 $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
417 foreach ($GenreLookup as $key => $value) {
418 if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
419 return $key;
420 }
421 }
422 return false;
423 }
424
425 /**
426 * @param string $OriginalGenre
427 *
428 * @return string|false
429 */
430 public static function StandardiseID3v1GenreName($OriginalGenre) {
431 if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
432 return self::LookupGenreName($GenreID);
433 }
434 return $OriginalGenre;
435 }
436
437 /**
438 * @param string $title
439 * @param string $artist
440 * @param string $album
441 * @param string $year
442 * @param int $genreid
443 * @param string $comment
444 * @param int|string $track
445 *
446 * @return string
447 */
448 public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
449 $ID3v1Tag = 'TAG';
450 $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
451 $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
452 $ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
453 $ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT);
454 if (!empty($track) && ($track > 0) && ($track <= 255)) {
455 $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
456 $ID3v1Tag .= "\x00";
457 if (gettype($track) == 'string') {
458 $track = (int) $track;
459 }
460 $ID3v1Tag .= chr($track);
461 } else {
462 $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
463 }
464 if (($genreid < 0) || ($genreid > 147)) {
465 $genreid = 255; // 'unknown' genre
466 }
467 switch (gettype($genreid)) {
468 case 'string':
469 case 'integer':
470 $ID3v1Tag .= chr(intval($genreid));
471 break;
472 default:
473 $ID3v1Tag .= chr(255); // 'unknown' genre
474 break;
475 }
476
477 return $ID3v1Tag;
478 }
479
480}
481