1 <?php |
1 <?php |
|
2 |
2 ///////////////////////////////////////////////////////////////// |
3 ///////////////////////////////////////////////////////////////// |
3 /// getID3() by James Heinrich <info@getid3.org> // |
4 /// getID3() by James Heinrich <info@getid3.org> // |
4 // available at http://getid3.sourceforge.net // |
5 // available at https://github.com/JamesHeinrich/getID3 // |
5 // or http://www.getid3.org // |
6 // or https://www.getid3.org // |
6 // also https://github.com/JamesHeinrich/getID3 // |
7 // or http://getid3.sourceforge.net // |
7 ///////////////////////////////////////////////////////////////// |
8 // see readme.txt for more details // |
8 // See readme.txt for more details // |
|
9 ///////////////////////////////////////////////////////////////// |
9 ///////////////////////////////////////////////////////////////// |
10 // // |
10 // // |
11 // module.tag.id3v1.php // |
11 // module.tag.id3v1.php // |
12 // module for analyzing ID3v1 tags // |
12 // module for analyzing ID3v1 tags // |
13 // dependencies: NONE // |
13 // dependencies: NONE // |
14 // /// |
14 // /// |
15 ///////////////////////////////////////////////////////////////// |
15 ///////////////////////////////////////////////////////////////// |
16 |
16 |
|
17 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers |
|
18 exit; |
|
19 } |
17 |
20 |
18 class getid3_id3v1 extends getid3_handler |
21 class getid3_id3v1 extends getid3_handler |
19 { |
22 { |
20 |
23 /** |
|
24 * @return bool |
|
25 */ |
21 public function Analyze() { |
26 public function Analyze() { |
22 $info = &$this->getid3->info; |
27 $info = &$this->getid3->info; |
23 |
28 |
24 if (!getid3_lib::intValueSupported($info['filesize'])) { |
29 if (!getid3_lib::intValueSupported($info['filesize'])) { |
25 $this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'); |
30 $this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'); |
41 $ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them |
46 $ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them |
42 $ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1)); |
47 $ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1)); |
43 |
48 |
44 // If second-last byte of comment field is null and last byte of comment field is non-null |
49 // 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 |
50 // 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")) { |
51 if (($id3v1tag[125] === "\x00") && ($id3v1tag[126] !== "\x00")) { |
47 $ParsedID3v1['track'] = ord(substr($ParsedID3v1['comment'], 29, 1)); |
52 $ParsedID3v1['track_number'] = ord(substr($ParsedID3v1['comment'], 29, 1)); |
48 $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28); |
53 $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28); |
49 } |
54 } |
50 $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']); |
55 $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']); |
51 |
56 |
52 $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']); |
57 $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']); |
53 if (!empty($ParsedID3v1['genre'])) { |
58 if (!empty($ParsedID3v1['genre'])) { |
58 } |
63 } |
59 |
64 |
60 foreach ($ParsedID3v1 as $key => $value) { |
65 foreach ($ParsedID3v1 as $key => $value) { |
61 $ParsedID3v1['comments'][$key][0] = $value; |
66 $ParsedID3v1['comments'][$key][0] = $value; |
62 } |
67 } |
63 // ID3v1 encoding detection hack START |
68 $ID3v1encoding = $this->getid3->encoding_id3v1; |
64 // 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 |
69 if ($this->getid3->encoding_id3v1_autodetect) { |
65 // 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 |
70 // ID3v1 encoding detection hack START |
66 $ID3v1encoding = 'ISO-8859-1'; |
71 // 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 |
67 foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) { |
72 // 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 |
68 foreach ($valuearray as $key => $value) { |
73 foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) { |
69 if (preg_match('#^[\\x00-\\x40\\xA8\\B8\\x80-\\xFF]+$#', $value)) { |
74 foreach ($valuearray as $key => $value) { |
70 foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) { |
75 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) |
71 if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) { |
76 foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) { |
72 $ID3v1encoding = $id3v1_bad_encoding; |
77 if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) { |
73 break 3; |
78 $ID3v1encoding = $id3v1_bad_encoding; |
74 } elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) { |
79 $this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key); |
75 $ID3v1encoding = $id3v1_bad_encoding; |
80 break 3; |
76 break 3; |
81 } elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) { |
|
82 $ID3v1encoding = $id3v1_bad_encoding; |
|
83 $this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key); |
|
84 break 3; |
|
85 } |
77 } |
86 } |
78 } |
87 } |
79 } |
88 } |
80 } |
89 } |
81 } |
90 // ID3v1 encoding detection hack END |
82 // ID3v1 encoding detection hack END |
91 } |
83 |
92 |
84 // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces |
93 // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces |
85 $GoodFormatID3v1tag = $this->GenerateID3v1Tag( |
94 $GoodFormatID3v1tag = $this->GenerateID3v1Tag( |
86 $ParsedID3v1['title'], |
95 $ParsedID3v1['title'], |
87 $ParsedID3v1['artist'], |
96 $ParsedID3v1['artist'], |
88 $ParsedID3v1['album'], |
97 $ParsedID3v1['album'], |
89 $ParsedID3v1['year'], |
98 $ParsedID3v1['year'], |
90 (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false), |
99 (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false), |
91 $ParsedID3v1['comment'], |
100 $ParsedID3v1['comment'], |
92 (!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : '')); |
101 (!empty($ParsedID3v1['track_number']) ? $ParsedID3v1['track_number'] : '')); |
93 $ParsedID3v1['padding_valid'] = true; |
102 $ParsedID3v1['padding_valid'] = true; |
94 if ($id3v1tag !== $GoodFormatID3v1tag) { |
103 if ($id3v1tag !== $GoodFormatID3v1tag) { |
95 $ParsedID3v1['padding_valid'] = false; |
104 $ParsedID3v1['padding_valid'] = false; |
96 $this->warning('Some ID3v1 fields do not use NULL characters for padding'); |
105 $this->warning('Some ID3v1 fields do not use NULL characters for padding'); |
97 } |
106 } |
326 } |
351 } |
327 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); |
352 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); |
328 return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); |
353 return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); |
329 } |
354 } |
330 |
355 |
|
356 /** |
|
357 * @param string $genre |
|
358 * @param bool $allowSCMPXextended |
|
359 * |
|
360 * @return string|false |
|
361 */ |
331 public static function LookupGenreID($genre, $allowSCMPXextended=false) { |
362 public static function LookupGenreID($genre, $allowSCMPXextended=false) { |
332 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); |
363 $GenreLookup = self::ArrayOfGenres($allowSCMPXextended); |
333 $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre)); |
364 $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre)); |
334 foreach ($GenreLookup as $key => $value) { |
365 foreach ($GenreLookup as $key => $value) { |
335 if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) { |
366 if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) { |
337 } |
368 } |
338 } |
369 } |
339 return false; |
370 return false; |
340 } |
371 } |
341 |
372 |
|
373 /** |
|
374 * @param string $OriginalGenre |
|
375 * |
|
376 * @return string|false |
|
377 */ |
342 public static function StandardiseID3v1GenreName($OriginalGenre) { |
378 public static function StandardiseID3v1GenreName($OriginalGenre) { |
343 if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) { |
379 if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) { |
344 return self::LookupGenreName($GenreID); |
380 return self::LookupGenreName($GenreID); |
345 } |
381 } |
346 return $OriginalGenre; |
382 return $OriginalGenre; |
347 } |
383 } |
348 |
384 |
|
385 /** |
|
386 * @param string $title |
|
387 * @param string $artist |
|
388 * @param string $album |
|
389 * @param string $year |
|
390 * @param int $genreid |
|
391 * @param string $comment |
|
392 * @param int|string $track |
|
393 * |
|
394 * @return string |
|
395 */ |
349 public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { |
396 public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { |
350 $ID3v1Tag = 'TAG'; |
397 $ID3v1Tag = 'TAG'; |
351 $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT); |
398 $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT); |
352 $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT); |
399 $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT); |
353 $ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT); |
400 $ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT); |