File indexing completed on 2025-03-09 05:22:18
0001 <?php 0002 ///////////////////////////////////////////////////////////////// 0003 /// getID3() by James Heinrich <info@getid3.org> // 0004 // available at http://getid3.sourceforge.net // 0005 // or http://www.getid3.org // 0006 // also https://github.com/JamesHeinrich/getID3 // 0007 ///////////////////////////////////////////////////////////////// 0008 // See readme.txt for more details // 0009 ///////////////////////////////////////////////////////////////// 0010 /// // 0011 // module.tag.lyrics3.php // 0012 // module for analyzing Lyrics3 tags // 0013 // dependencies: module.tag.apetag.php (optional) // 0014 // /// 0015 ///////////////////////////////////////////////////////////////// 0016 0017 0018 class getid3_lyrics3 extends getid3_handler 0019 { 0020 0021 public function Analyze() { 0022 $info = &$this->getid3->info; 0023 0024 // http://www.volweb.cz/str/tags.htm 0025 0026 if (!getid3_lib::intValueSupported($info['filesize'])) { 0027 $info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; 0028 return false; 0029 } 0030 0031 $this->fseek((0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size] 0032 $lyrics3_id3v1 = $this->fread(128 + 9 + 6); 0033 $lyrics3lsz = substr($lyrics3_id3v1, 0, 6); // Lyrics3size 0034 $lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200 0035 $id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1 0036 0037 if ($lyrics3end == 'LYRICSEND') { 0038 // Lyrics3v1, ID3v1, no APE 0039 0040 $lyrics3size = 5100; 0041 $lyrics3offset = $info['filesize'] - 128 - $lyrics3size; 0042 $lyrics3version = 1; 0043 0044 } elseif ($lyrics3end == 'LYRICS200') { 0045 // Lyrics3v2, ID3v1, no APE 0046 0047 // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' 0048 $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); 0049 $lyrics3offset = $info['filesize'] - 128 - $lyrics3size; 0050 $lyrics3version = 2; 0051 0052 } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) { 0053 // Lyrics3v1, no ID3v1, no APE 0054 0055 $lyrics3size = 5100; 0056 $lyrics3offset = $info['filesize'] - $lyrics3size; 0057 $lyrics3version = 1; 0058 $lyrics3offset = $info['filesize'] - $lyrics3size; 0059 0060 } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) { 0061 0062 // Lyrics3v2, no ID3v1, no APE 0063 0064 $lyrics3size = strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' 0065 $lyrics3offset = $info['filesize'] - $lyrics3size; 0066 $lyrics3version = 2; 0067 0068 } else { 0069 0070 if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) { 0071 0072 $this->fseek($info['ape']['tag_offset_start'] - 15); 0073 $lyrics3lsz = $this->fread(6); 0074 $lyrics3end = $this->fread(9); 0075 0076 if ($lyrics3end == 'LYRICSEND') { 0077 // Lyrics3v1, APE, maybe ID3v1 0078 0079 $lyrics3size = 5100; 0080 $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size; 0081 $info['avdataend'] = $lyrics3offset; 0082 $lyrics3version = 1; 0083 $info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; 0084 0085 } elseif ($lyrics3end == 'LYRICS200') { 0086 // Lyrics3v2, APE, maybe ID3v1 0087 0088 $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' 0089 $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size; 0090 $lyrics3version = 2; 0091 $info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; 0092 0093 } 0094 0095 } 0096 0097 } 0098 0099 if (isset($lyrics3offset)) { 0100 $info['avdataend'] = $lyrics3offset; 0101 $this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size); 0102 0103 if (!isset($info['ape'])) { 0104 $GETID3_ERRORARRAY = &$info['warning']; 0105 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true); 0106 $getid3_temp = new getID3(); 0107 $getid3_temp->openfile($this->getid3->filename); 0108 $getid3_apetag = new getid3_apetag($getid3_temp); 0109 $getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start']; 0110 $getid3_apetag->Analyze(); 0111 if (!empty($getid3_temp->info['ape'])) { 0112 $info['ape'] = $getid3_temp->info['ape']; 0113 } 0114 if (!empty($getid3_temp->info['replay_gain'])) { 0115 $info['replay_gain'] = $getid3_temp->info['replay_gain']; 0116 } 0117 unset($getid3_temp, $getid3_apetag); 0118 } 0119 0120 } 0121 0122 return true; 0123 } 0124 0125 public function getLyrics3Data($endoffset, $version, $length) { 0126 // http://www.volweb.cz/str/tags.htm 0127 0128 $info = &$this->getid3->info; 0129 0130 if (!getid3_lib::intValueSupported($endoffset)) { 0131 $info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB'; 0132 return false; 0133 } 0134 0135 $this->fseek($endoffset); 0136 if ($length <= 0) { 0137 return false; 0138 } 0139 $rawdata = $this->fread($length); 0140 0141 $ParsedLyrics3['raw']['lyrics3version'] = $version; 0142 $ParsedLyrics3['raw']['lyrics3tagsize'] = $length; 0143 $ParsedLyrics3['tag_offset_start'] = $endoffset; 0144 $ParsedLyrics3['tag_offset_end'] = $endoffset + $length - 1; 0145 0146 if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') { 0147 if (strpos($rawdata, 'LYRICSBEGIN') !== false) { 0148 0149 $info['warning'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version; 0150 $info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN'); 0151 $rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN')); 0152 $length = strlen($rawdata); 0153 $ParsedLyrics3['tag_offset_start'] = $info['avdataend']; 0154 $ParsedLyrics3['raw']['lyrics3tagsize'] = $length; 0155 0156 } else { 0157 0158 $info['error'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead'; 0159 return false; 0160 0161 } 0162 0163 } 0164 0165 switch ($version) { 0166 0167 case 1: 0168 if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICSEND') { 0169 $ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9)); 0170 $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); 0171 } else { 0172 $info['error'][] = '"LYRICSEND" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; 0173 return false; 0174 } 0175 break; 0176 0177 case 2: 0178 if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICS200') { 0179 $ParsedLyrics3['raw']['unparsed'] = substr($rawdata, 11, strlen($rawdata) - 11 - 9 - 6); // LYRICSBEGIN + LYRICS200 + LSZ 0180 $rawdata = $ParsedLyrics3['raw']['unparsed']; 0181 while (strlen($rawdata) > 0) { 0182 $fieldname = substr($rawdata, 0, 3); 0183 $fieldsize = (int) substr($rawdata, 3, 5); 0184 $ParsedLyrics3['raw'][$fieldname] = substr($rawdata, 8, $fieldsize); 0185 $rawdata = substr($rawdata, 3 + 5 + $fieldsize); 0186 } 0187 0188 if (isset($ParsedLyrics3['raw']['IND'])) { 0189 $i = 0; 0190 $flagnames = array('lyrics', 'timestamps', 'inhibitrandom'); 0191 foreach ($flagnames as $flagname) { 0192 if (strlen($ParsedLyrics3['raw']['IND']) > $i++) { 0193 $ParsedLyrics3['flags'][$flagname] = $this->IntString2Bool(substr($ParsedLyrics3['raw']['IND'], $i, 1 - 1)); 0194 } 0195 } 0196 } 0197 0198 $fieldnametranslation = array('ETT'=>'title', 'EAR'=>'artist', 'EAL'=>'album', 'INF'=>'comment', 'AUT'=>'author'); 0199 foreach ($fieldnametranslation as $key => $value) { 0200 if (isset($ParsedLyrics3['raw'][$key])) { 0201 $ParsedLyrics3['comments'][$value][] = trim($ParsedLyrics3['raw'][$key]); 0202 } 0203 } 0204 0205 if (isset($ParsedLyrics3['raw']['IMG'])) { 0206 $imagestrings = explode("\r\n", $ParsedLyrics3['raw']['IMG']); 0207 foreach ($imagestrings as $key => $imagestring) { 0208 if (strpos($imagestring, '||') !== false) { 0209 $imagearray = explode('||', $imagestring); 0210 $ParsedLyrics3['images'][$key]['filename'] = (isset($imagearray[0]) ? $imagearray[0] : ''); 0211 $ParsedLyrics3['images'][$key]['description'] = (isset($imagearray[1]) ? $imagearray[1] : ''); 0212 $ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(isset($imagearray[2]) ? $imagearray[2] : ''); 0213 } 0214 } 0215 } 0216 if (isset($ParsedLyrics3['raw']['LYR'])) { 0217 $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); 0218 } 0219 } else { 0220 $info['error'][] = '"LYRICS200" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; 0221 return false; 0222 } 0223 break; 0224 0225 default: 0226 $info['error'][] = 'Cannot process Lyrics3 version '.$version.' (only v1 and v2)'; 0227 return false; 0228 break; 0229 } 0230 0231 0232 if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end'])) { 0233 $info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data'; 0234 unset($info['id3v1']); 0235 foreach ($info['warning'] as $key => $value) { 0236 if ($value == 'Some ID3v1 fields do not use NULL characters for padding') { 0237 unset($info['warning'][$key]); 0238 sort($info['warning']); 0239 break; 0240 } 0241 } 0242 } 0243 0244 $info['lyrics3'] = $ParsedLyrics3; 0245 0246 return true; 0247 } 0248 0249 public function Lyrics3Timestamp2Seconds($rawtimestamp) { 0250 if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) { 0251 return (int) (($regs[1] * 60) + $regs[2]); 0252 } 0253 return false; 0254 } 0255 0256 public function Lyrics3LyricsTimestampParse(&$Lyrics3data) { 0257 $lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']); 0258 foreach ($lyricsarray as $key => $lyricline) { 0259 $regs = array(); 0260 unset($thislinetimestamps); 0261 while (preg_match('#^(\\[[0-9]{2}:[0-9]{2}\\])#', $lyricline, $regs)) { 0262 $thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]); 0263 $lyricline = str_replace($regs[0], '', $lyricline); 0264 } 0265 $notimestamplyricsarray[$key] = $lyricline; 0266 if (isset($thislinetimestamps) && is_array($thislinetimestamps)) { 0267 sort($thislinetimestamps); 0268 foreach ($thislinetimestamps as $timestampkey => $timestamp) { 0269 if (isset($Lyrics3data['synchedlyrics'][$timestamp])) { 0270 // timestamps only have a 1-second resolution, it's possible that multiple lines 0271 // could have the same timestamp, if so, append 0272 $Lyrics3data['synchedlyrics'][$timestamp] .= "\r\n".$lyricline; 0273 } else { 0274 $Lyrics3data['synchedlyrics'][$timestamp] = $lyricline; 0275 } 0276 } 0277 } 0278 } 0279 $Lyrics3data['unsynchedlyrics'] = implode("\r\n", $notimestamplyricsarray); 0280 if (isset($Lyrics3data['synchedlyrics']) && is_array($Lyrics3data['synchedlyrics'])) { 0281 ksort($Lyrics3data['synchedlyrics']); 0282 } 0283 return true; 0284 } 0285 0286 public function IntString2Bool($char) { 0287 if ($char == '1') { 0288 return true; 0289 } elseif ($char == '0') { 0290 return false; 0291 } 0292 return null; 0293 } 0294 }