rlm@3: | rlm@3: // | Allan Hansen | rlm@3: // +----------------------------------------------------------------------+ rlm@3: // | module.tag.lyrics3.php | rlm@3: // | module for analyzing Lyrics3 tags | rlm@3: // | dependencies: module.tag.apetag.php (optional) | rlm@3: // +----------------------------------------------------------------------+ rlm@3: // rlm@3: // $Id: module.tag.lyrics3.php,v 1.5 2006/11/16 22:04:23 ah Exp $ rlm@3: rlm@3: rlm@3: class getid3_lyrics3 extends getid3_handler rlm@3: { rlm@3: rlm@3: public function Analyze() { rlm@3: rlm@3: $getid3 = $this->getid3; rlm@3: rlm@3: fseek($getid3->fp, (0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - LYRICSEND - [Lyrics3size] rlm@3: $lyrics3_id3v1 = fread($getid3->fp, 128 + 9 + 6); rlm@3: $lyrics3_lsz = substr($lyrics3_id3v1, 0, 6); // Lyrics3size rlm@3: $lyrics3_end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200 rlm@3: $id3v1_tag = substr($lyrics3_id3v1, 15, 128); // ID3v1 rlm@3: rlm@3: // Lyrics3v1, ID3v1, no APE rlm@3: if ($lyrics3_end == 'LYRICSEND') { rlm@3: rlm@3: $lyrics3_size = 5100; rlm@3: $lyrics3_offset = filesize($getid3->filename) - 128 - $lyrics3_size; rlm@3: $lyrics3_version = 1; rlm@3: } rlm@3: rlm@3: // Lyrics3v2, ID3v1, no APE rlm@3: elseif ($lyrics3_end == 'LYRICS200') { rlm@3: rlm@3: // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' rlm@3: $lyrics3_size = $lyrics3_lsz + 6 + strlen('LYRICS200'); rlm@3: $lyrics3_offset = filesize($getid3->filename) - 128 - $lyrics3_size; rlm@3: $lyrics3_version = 2; rlm@3: } rlm@3: rlm@3: // Lyrics3v1, no ID3v1, no APE rlm@3: elseif (substr(strrev($lyrics3_id3v1), 0, 9) == 'DNESCIRYL') { // strrev('LYRICSEND') = 'DNESCIRYL' rlm@3: rlm@3: $lyrics3_size = 5100; rlm@3: $lyrics3_offset = filesize($getid3->filename) - $lyrics3_size; rlm@3: $lyrics3_version = 1; rlm@3: $lyrics3_offset = filesize($getid3->filename) - $lyrics3_size; rlm@3: } rlm@3: rlm@3: // Lyrics3v2, no ID3v1, no APE rlm@3: elseif (substr(strrev($lyrics3_id3v1), 0, 9) == '002SCIRYL') { // strrev('LYRICS200') = '002SCIRYL' rlm@3: rlm@3: $lyrics3_size = strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 15; // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' // 15 = 6 + strlen('LYRICS200') rlm@3: $lyrics3_offset = filesize($getid3->filename) - $lyrics3_size; rlm@3: $lyrics3_version = 2; rlm@3: } rlm@3: rlm@3: elseif (isset($getid3->info['ape']['tag_offset_start']) && ($getid3->info['ape']['tag_offset_start'] > 15)) { rlm@3: rlm@3: fseek($getid3->fp, $getid3->info['ape']['tag_offset_start'] - 15, SEEK_SET); rlm@3: $lyrics3_lsz = fread($getid3->fp, 6); rlm@3: $lyrics3_end = fread($getid3->fp, 9); rlm@3: rlm@3: rlm@3: // Lyrics3v1, APE, maybe ID3v1 rlm@3: if ($lyrics3_end == 'LYRICSEND') { rlm@3: rlm@3: $lyrics3_size = 5100; rlm@3: $lyrics3_offset = $getid3->info['ape']['tag_offset_start'] - $lyrics3_size; rlm@3: $getid3->info['avdataend'] = $lyrics3_offset; rlm@3: $lyrics3_version = 1; rlm@3: $getid3->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability'); rlm@3: } rlm@3: rlm@3: rlm@3: // Lyrics3v2, APE, maybe ID3v1 rlm@3: elseif ($lyrics3_end == 'LYRICS200') { rlm@3: rlm@3: $lyrics3_size = $lyrics3_lsz + 15; // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' rlm@3: $lyrics3_offset = $getid3->info['ape']['tag_offset_start'] - $lyrics3_size; rlm@3: $lyrics3_version = 2; rlm@3: $getid3->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability'); rlm@3: rlm@3: } rlm@3: } rlm@3: rlm@3: rlm@3: //// GetLyrics3Data() rlm@3: rlm@3: rlm@3: if (isset($lyrics3_offset)) { rlm@3: rlm@3: $getid3->info['avdataend'] = $lyrics3_offset; rlm@3: rlm@3: if ($lyrics3_size <= 0) { rlm@3: return false; rlm@3: } rlm@3: rlm@3: fseek($getid3->fp, $lyrics3_offset, SEEK_SET); rlm@3: $raw_data = fread($getid3->fp, $lyrics3_size); rlm@3: rlm@3: if (substr($raw_data, 0, 11) != 'LYRICSBEGIN') { rlm@3: if (strpos($raw_data, 'LYRICSBEGIN') !== false) { rlm@3: rlm@3: $getid3->warning('"LYRICSBEGIN" expected at '.$lyrics3_offset.' but actually found at '.($lyrics3_offset + strpos($raw_data, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$lyrics3_version); rlm@3: $getid3->info['avdataend'] = $lyrics3_offset + strpos($raw_data, 'LYRICSBEGIN'); rlm@3: $parsed_lyrics3['tag_offset_start'] = $getid3->info['avdataend']; rlm@3: $raw_data = substr($raw_data, strpos($raw_data, 'LYRICSBEGIN')); rlm@3: $lyrics3_size = strlen($raw_data); rlm@3: } rlm@3: else { rlm@3: throw new getid3_exception('"LYRICSBEGIN" expected at '.$lyrics3_offset.' but found "'.substr($raw_data, 0, 11).'" instead.'); rlm@3: } rlm@3: rlm@3: } rlm@3: rlm@3: $parsed_lyrics3['raw']['lyrics3version'] = $lyrics3_version; rlm@3: $parsed_lyrics3['raw']['lyrics3tagsize'] = $lyrics3_size; rlm@3: $parsed_lyrics3['tag_offset_start'] = $lyrics3_offset; rlm@3: $parsed_lyrics3['tag_offset_end'] = $lyrics3_offset + $lyrics3_size; rlm@3: rlm@3: switch ($lyrics3_version) { rlm@3: rlm@3: case 1: rlm@3: if (substr($raw_data, strlen($raw_data) - 9, 9) == 'LYRICSEND') { rlm@3: $parsed_lyrics3['raw']['LYR'] = trim(substr($raw_data, 11, strlen($raw_data) - 11 - 9)); rlm@3: getid3_lyrics3::Lyrics3LyricsTimestampParse($parsed_lyrics3); rlm@3: } rlm@3: else { rlm@3: throw new getid3_exception('"LYRICSEND" expected at '.(ftell($getid3->fp) - 11 + $lyrics3_size - 9).' but found "'.substr($raw_data, strlen($raw_data) - 9, 9).'" instead.'); rlm@3: } rlm@3: break; rlm@3: rlm@3: case 2: rlm@3: if (substr($raw_data, strlen($raw_data) - 9, 9) == 'LYRICS200') { rlm@3: $parsed_lyrics3['raw']['unparsed'] = substr($raw_data, 11, strlen($raw_data) - 11 - 9 - 6); // LYRICSBEGIN + LYRICS200 + LSZ rlm@3: $raw_data = $parsed_lyrics3['raw']['unparsed']; rlm@3: while (strlen($raw_data) > 0) { rlm@3: $fieldname = substr($raw_data, 0, 3); rlm@3: $fieldsize = (int)substr($raw_data, 3, 5); rlm@3: $parsed_lyrics3['raw'][$fieldname] = substr($raw_data, 8, $fieldsize); rlm@3: $raw_data = substr($raw_data, 3 + 5 + $fieldsize); rlm@3: } rlm@3: rlm@3: if (isset($parsed_lyrics3['raw']['IND'])) { rlm@3: $i = 0; rlm@3: foreach (array ('lyrics', 'timestamps', 'inhibitrandom') as $flagname) { rlm@3: if (strlen($parsed_lyrics3['raw']['IND']) > ++$i) { rlm@3: $parsed_lyrics3['flags'][$flagname] = getid3_lyrics3::IntString2Bool(substr($parsed_lyrics3['raw']['IND'], $i, 1)); rlm@3: } rlm@3: } rlm@3: } rlm@3: rlm@3: foreach (array ('ETT'=>'title', 'EAR'=>'artist', 'EAL'=>'album', 'INF'=>'comment', 'AUT'=>'author') as $key => $value) { rlm@3: if (isset($parsed_lyrics3['raw'][$key])) { rlm@3: $parsed_lyrics3['comments'][$value][] = trim($parsed_lyrics3['raw'][$key]); rlm@3: } rlm@3: } rlm@3: rlm@3: if (isset($parsed_lyrics3['raw']['IMG'])) { rlm@3: foreach (explode("\r\n", $parsed_lyrics3['raw']['IMG']) as $key => $image_string) { rlm@3: if (strpos($image_string, '||') !== false) { rlm@3: $imagearray = explode('||', $image_string); rlm@3: $parsed_lyrics3['images'][$key]['filename'] = @$imagearray[0]; rlm@3: $parsed_lyrics3['images'][$key]['description'] = @$imagearray[1]; rlm@3: $parsed_lyrics3['images'][$key]['timestamp'] = getid3_lyrics3::Lyrics3Timestamp2Seconds(@$imagearray[2]); rlm@3: } rlm@3: } rlm@3: } rlm@3: rlm@3: if (isset($parsed_lyrics3['raw']['LYR'])) { rlm@3: getid3_lyrics3::Lyrics3LyricsTimestampParse($parsed_lyrics3); rlm@3: } rlm@3: } rlm@3: else { rlm@3: throw new getid3_exception('"LYRICS200" expected at '.(ftell($getid3->fp) - 11 + $lyrics3_size - 9).' but found "'.substr($raw_data, strlen($raw_data) - 9, 9).'" instead.'); rlm@3: } rlm@3: break; rlm@3: rlm@3: default: rlm@3: throw new getid3_exception('Cannot process Lyrics3 version '.$lyrics3_version.' (only v1 and v2)'); rlm@3: } rlm@3: rlm@3: if (isset($getid3->info['id3v1']['tag_offset_start']) && ($getid3->info['id3v1']['tag_offset_start'] < $parsed_lyrics3['tag_offset_end'])) { rlm@3: $getid3->warning('ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data'); rlm@3: unset($getid3->info['id3v1']); rlm@3: } rlm@3: rlm@3: $getid3->info['lyrics3'] = $parsed_lyrics3; rlm@3: rlm@3: rlm@3: // Check for APE tag after lyrics3 rlm@3: if (!@$getid3->info['ape'] && $getid3->option_tag_apetag && class_exists('getid3_apetag')) { rlm@3: $apetag = new getid3_apetag($getid3); rlm@3: $apetag->option_override_end_offset = $getid3->info['lyrics3']['tag_offset_start']; rlm@3: $apetag->Analyze(); rlm@3: } rlm@3: } rlm@3: rlm@3: return true; rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: rlm@3: public static function Lyrics3Timestamp2Seconds($rawtimestamp) { rlm@3: if (ereg('^\\[([0-9]{2}):([0-9]{2})\\]$', $rawtimestamp, $regs)) { rlm@3: return (int)(($regs[1] * 60) + $regs[2]); rlm@3: } rlm@3: return false; rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function Lyrics3LyricsTimestampParse(&$lyrics3_data) { rlm@3: rlm@3: $lyrics_array = explode("\r\n", $lyrics3_data['raw']['LYR']); rlm@3: foreach ($lyrics_array as $key => $lyric_line) { rlm@3: rlm@3: while (ereg('^(\\[[0-9]{2}:[0-9]{2}\\])', $lyric_line, $regs)) { rlm@3: $this_line_timestamps[] = getid3_lyrics3::Lyrics3Timestamp2Seconds($regs[0]); rlm@3: $lyric_line = str_replace($regs[0], '', $lyric_line); rlm@3: } rlm@3: $no_timestamp_lyrics_array[$key] = $lyric_line; rlm@3: if (@is_array($this_line_timestamps)) { rlm@3: sort($this_line_timestamps); rlm@3: foreach ($this_line_timestamps as $timestampkey => $timestamp) { rlm@3: if (isset($lyrics3_data['synchedlyrics'][$timestamp])) { rlm@3: // timestamps only have a 1-second resolution, it's possible that multiple lines rlm@3: // could have the same timestamp, if so, append rlm@3: $lyrics3_data['synchedlyrics'][$timestamp] .= "\r\n".$lyric_line; rlm@3: } else { rlm@3: $lyrics3_data['synchedlyrics'][$timestamp] = $lyric_line; rlm@3: } rlm@3: } rlm@3: } rlm@3: unset($this_line_timestamps); rlm@3: $regs = array (); rlm@3: } rlm@3: $lyrics3_data['unsynchedlyrics'] = implode("\r\n", $no_timestamp_lyrics_array); rlm@3: if (isset($lyrics3_data['synchedlyrics']) && is_array($lyrics3_data['synchedlyrics'])) { rlm@3: ksort($lyrics3_data['synchedlyrics']); rlm@3: } rlm@3: return true; rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function IntString2Bool($char) { rlm@3: rlm@3: return $char == '1' ? true : ($char == '0' ? false : null); rlm@3: } rlm@3: } rlm@3: rlm@3: rlm@3: ?>