wp/wp-includes/ID3/module.tag.id3v1.php
changeset 0 d970ebf37754
child 5 5e2f62d02dcd
equal deleted inserted replaced
-1:000000000000 0:d970ebf37754
       
     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 }