📄 module.tag.id3v2.php
字号:
<?php//////////////////////////////////////////////////////////////////// getID3() by James Heinrich <info@getid3.org> //// available at http://getid3.sourceforge.net //// or http://www.getid3.org ///////////////////////////////////////////////////////////////////// See readme.txt for more details ////////////////////////////////////////////////////////////////////// //// module.tag.id3v2.php //// module for analyzing ID3v2 tags //// dependencies: module.tag.id3v1.php //// ////////////////////////////////////////////////////////////////////getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);class getid3_id3v2{ function getid3_id3v2(&$fd, &$ThisFileInfo, $StartingOffset=0) { // Overall tag structure: // +-----------------------------+ // | Header (10 bytes) | // +-----------------------------+ // | Extended Header | // | (variable length, OPTIONAL) | // +-----------------------------+ // | Frames (variable length) | // +-----------------------------+ // | Padding | // | (variable length, OPTIONAL) | // +-----------------------------+ // | Footer (10 bytes, OPTIONAL) | // +-----------------------------+ // Header // ID3v2/file identifier "ID3" // ID3v2 version $04 00 // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x) // ID3v2 size 4 * %0xxxxxxx // shortcuts $ThisFileInfo['id3v2']['header'] = true; $thisfile_id3v2 = &$ThisFileInfo['id3v2']; $thisfile_id3v2['flags'] = array(); $thisfile_id3v2_flags = &$thisfile_id3v2['flags']; fseek($fd, $StartingOffset, SEEK_SET); $header = fread($fd, 10); if (substr($header, 0, 3) == 'ID3') { $thisfile_id3v2['majorversion'] = ord($header{3}); $thisfile_id3v2['minorversion'] = ord($header{4}); // shortcut $id3v2_majorversion = &$thisfile_id3v2['majorversion']; } else { unset($ThisFileInfo['id3v2']); return false; } if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists) $ThisFileInfo['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']; return false; } $id3_flags = ord($header{5}); switch ($id3v2_majorversion) { case 2: // %ab000000 in v2.2 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression break; case 3: // %abc00000 in v2.3 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator break; case 4: // %abcd0000 in v2.4 $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present break; } $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length $thisfile_id3v2['tag_offset_start'] = $StartingOffset; $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength']; // Extended Header if (isset($thisfile_id3v2_flags['exthead']) && $thisfile_id3v2_flags['exthead']) { // Extended header size 4 * %0xxxxxxx // Number of flag bytes $01 // Extended Flags $xx // Where the 'Extended header size' is the size of the whole extended header, stored as a 32 bit synchsafe integer. $thisfile_id3v2['exthead_length'] = getid3_lib::BigEndian2Int(fread($fd, 4), 1); $thisfile_id3v2['exthead_flag_bytes'] = ord(fread($fd, 1)); if ($thisfile_id3v2['exthead_flag_bytes'] == 1) { // The extended flags field, with its size described by 'number of flag bytes', is defined as: // %0bcd0000 // b - Tag is an update // Flag data length $00 // c - CRC data present // Flag data length $05 // Total frame CRC 5 * %0xxxxxxx // d - Tag restrictions // Flag data length $01 $extheaderflags = fread($fd, $thisfile_id3v2['exthead_flag_bytes']); $id3_exthead_flags = getid3_lib::BigEndian2Bin(substr($header, 5, 1)); $thisfile_id3v2['exthead_flags']['update'] = substr($id3_exthead_flags, 1, 1); $thisfile_id3v2['exthead_flags']['CRC'] = substr($id3_exthead_flags, 2, 1); if ($thisfile_id3v2['exthead_flags']['CRC']) { $extheaderrawCRC = fread($fd, 5); $thisfile_id3v2['exthead_flags']['CRC'] = getid3_lib::BigEndian2Int($extheaderrawCRC, 1); } $thisfile_id3v2['exthead_flags']['restrictions'] = substr($id3_exthead_flags, 3, 1); if ($thisfile_id3v2['exthead_flags']['restrictions']) { // Restrictions %ppqrrstt $extheaderrawrestrictions = fread($fd, 1); $thisfile_id3v2['exthead_flags']['restrictions_tagsize'] = (bindec('11000000') & ord($extheaderrawrestrictions)) >> 6; // p - Tag size restrictions $thisfile_id3v2['exthead_flags']['restrictions_textenc'] = (bindec('00100000') & ord($extheaderrawrestrictions)) >> 5; // q - Text encoding restrictions $thisfile_id3v2['exthead_flags']['restrictions_textsize'] = (bindec('00011000') & ord($extheaderrawrestrictions)) >> 3; // r - Text fields size restrictions $thisfile_id3v2['exthead_flags']['restrictions_imgenc'] = (bindec('00000100') & ord($extheaderrawrestrictions)) >> 2; // s - Image encoding restrictions $thisfile_id3v2['exthead_flags']['restrictions_imgsize'] = (bindec('00000011') & ord($extheaderrawrestrictions)) >> 0; // t - Image size restrictions } } else { $ThisFileInfo['warning'][] = '$thisfile_id3v2[exthead_flag_bytes] = "'.$thisfile_id3v2['exthead_flag_bytes'].'" (expecting "1")'; fseek($fd, $thisfile_id3v2['exthead_length'] - 1, SEEK_CUR); //return false; } } // end extended header // create 'encoding' key - used by getid3::HandleAllTags() // in ID3v2 every field can have it's own encoding type // so force everything to UTF-8 so it can be handled consistantly $thisfile_id3v2['encoding'] = 'UTF-8'; // Frames // All ID3v2 frames consists of one frame header followed by one or more // fields containing the actual information. The header is always 10 // bytes and laid out as follows: // // Frame ID $xx xx xx xx (four characters) // Size 4 * %0xxxxxxx // Flags $xx xx $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header if (@$thisfile_id3v2['exthead_length']) { $sizeofframes -= ($thisfile_id3v2['exthead_length'] + 4); } if (@$thisfile_id3v2_flags['isfooter']) { $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio } if ($sizeofframes > 0) { $framedata = fread($fd, $sizeofframes); // read all frames from file into $framedata variable // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x) if (@$thisfile_id3v2_flags['unsynch'] && ($id3v2_majorversion <= 3)) { $framedata = $this->DeUnsynchronise($framedata); } // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead // of on tag level, making it easier to skip frames, increasing the streamability // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that // there exists an unsynchronised frame, while the new unsynchronisation flag in // the frame header [S:4.1.2] indicates unsynchronisation. $framedataoffset = 10 + (@$thisfile_id3v2['exthead_length'] ? $thisfile_id3v2['exthead_length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present) while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) { // insufficient room left in ID3v2 header for actual data - must be padding $thisfile_id3v2['padding']['start'] = $framedataoffset; $thisfile_id3v2['padding']['length'] = strlen($framedata); $thisfile_id3v2['padding']['valid'] = true; for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) { if ($framedata{$i} != "\x00") { $thisfile_id3v2['padding']['valid'] = false; $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; $ThisFileInfo['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; break; } } break; // skip rest of ID3v2 header } if ($id3v2_majorversion == 2) { // Frame ID $xx xx xx (three characters) // Size $xx xx xx (24-bit integer) // Flags $xx xx $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header $framedata = substr($framedata, 6); // and leave the rest in $framedata $frame_name = substr($frame_header, 0, 3); $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0); $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs } elseif ($id3v2_majorversion > 2) { // Frame ID $xx xx xx xx (four characters) // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+) // Flags $xx xx $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header $framedata = substr($framedata, 10); // and leave the rest in $framedata $frame_name = substr($frame_header, 0, 4); if ($id3v2_majorversion == 3) { $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer } else { // ID3v2.4+ $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value) } if ($frame_size < (strlen($framedata) + 4)) { $nextFrameID = substr($framedata, $frame_size, 4); if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) { // next frame is OK } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) { // MP3ext known broken frames - "ok" for the purposes of this test } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) { $ThisFileInfo['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3'; $id3v2_majorversion = 3; $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer } } $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2)); } if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) { // padding encountered
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -