Problem parsing ID3v2.4

Locked
sven
User
Posts:1
Joined:Fri Jun 17, 2011 11:34 pm
Are you a spambot?:no
Problem parsing ID3v2.4

Post by sven » Fri Jun 17, 2011 11:43 pm

Hello!

I had problems with MP3 with ID3v2.4 Tags, because the extended header and
compression was calculated wrong.
I added a patch, which addresses the problems...

Regards
Sven

Here is the patch (because uploading attachments does not work).
If you want, I sent it via e-mail too...

The patch corrects the following problems of the getid3 version 1.8.5 (2011-02-18)
- Wrong calculation of extended header
- Wrong evaluation of extended header flags
- Add warning for extended header length mismatch
- Compare length from DataLengthIndicator AFTER decompression
- Decompress frame data from beginning of stream

Code: Select all

--- getid3-1.8.5/getid3/module.tag.id3v2.php	2011-02-14 10:37:36.000000000 +0100
+++ getid3/module.tag.id3v2.php	2011-06-18 01:26:02.000000000 +0200
@@ -195,17 +195,19 @@
 					$extended_header_offset += 4;
 
 					$thisfile_id3v2['exthead']['flag_bytes'] = 1;
+					$extended_header_offset += 1;
 					$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
 					$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
 
-					$thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x4000);
-					$thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x2000);
-					$thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x1000);
+					$thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
+					$thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
+					$thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
 
 					if ($thisfile_id3v2['exthead']['flags']['crc']) {
 						$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 5), 1);
 						$extended_header_offset += 5;
 					}
+
 					if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
 						// %ppqrrstt
 						$restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
@@ -216,13 +218,17 @@
 						$thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw && 0x04) >> 2; // s - Image encoding restrictions
 						$thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw && 0x03) >> 0; // t - Image size restrictions
 					}
+					$extended_header_offset += 1;
 
+					if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
+						$ThisFileInfo['warning'][] = 'ID3v2.4 extended header length mismatch.';
+					}
 				}
+				
 				$framedataoffset += $extended_header_offset;
 				$framedata = substr($framedata, $extended_header_offset);
 			} // end extended header
 
-
 			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
@@ -512,9 +518,6 @@
 				if ($parsedFrame['flags']['DataLengthIndicator']) {
 					$parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
 					$parsedFrame['data']                  =                           substr($parsedFrame['data'], 4);
-					if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
-						$ThisFileInfo['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
-					}
 				}
 			}
 
@@ -525,7 +528,7 @@
 					$ThisFileInfo['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
 				} else {
 					ob_start();
-					if ($decompresseddata = gzuncompress(substr($parsedFrame['data'], 4))) {
+					if ($decompresseddata = gzuncompress(substr($parsedFrame['data'], 0))) {
 						ob_end_clean();
 						$parsedFrame['data'] = $decompresseddata;
 					} else {
@@ -535,6 +538,14 @@
 					}
 				}
 			}
+
+			# Now check (after decompression!)
+			if ($parsedFrame['flags']['DataLengthIndicator']) {
+				if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
+					$ThisFileInfo['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
+				}
+			}
+			
 		}
 
 		if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {

James Heinrich
getID3() v1 developer
Posts:1477
Joined:Fri May 04, 2001 4:00 pm
Are you a spambot?:no
Location:Northern Ontario, Canada
Contact:

Re: Problem parsing ID3v2.4

Post by James Heinrich » Mon Jun 20, 2011 4:39 pm

Hi sven,

Thanks for the patch. I'll look it over and get it included in v1.9.0, as appropriate.

Do you have a sample file with an ID3v2.4 extended header? I coded that section from documentation, but don't actually have a file to test with. Could you please email a sample file to info@getid3.org ? Thanks.

James Heinrich
getID3() v1 developer
Posts:1477
Joined:Fri May 04, 2001 4:00 pm
Are you a spambot?:no
Location:Northern Ontario, Canada
Contact:

Re: Problem parsing ID3v2.4

Post by James Heinrich » Mon Jun 20, 2011 7:34 pm

Thanks for the sample. After going through the sample file, and the documentation once again, I have fixed a few errors; some that you had caught and some we both had missed. The biggest of these was that any element flagged as being present in the extended header always has a data size byte (even though the sizes are hardcoded in the specs). I had missed this before, and so the extended header CRC returned was incorrect.

This is the code that will be included in v1.9.0, it should be easy enough to back-port it if you need it within the next day or two before that's released)

Code: Select all

} elseif ($id3v2_majorversion == 4) {

	// v2.4 definition:
	//Extended header size   4 * %0xxxxxxx // 28-bit synchsafe integer
	//Number of flag bytes       $01
	//Extended Flags             $xx
	//     %0bcd0000 // v2.4
	//     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

	$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
	$extended_header_offset += 4;

	$thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
	$extended_header_offset += 1;

	$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
	$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];

	$thisfile_id3v2['exthead']['flags']['update']       = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
	$thisfile_id3v2['exthead']['flags']['crc']          = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
	$thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);

	if ($thisfile_id3v2['exthead']['flags']['update']) {
		$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
		$extended_header_offset += 1;
	}

	if ($thisfile_id3v2['exthead']['flags']['crc']) {
		$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
		$extended_header_offset += 1;
		$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
		$extended_header_offset += $ext_header_chunk_length;
	}

	if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
		$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
		$extended_header_offset += 1;

		// %ppqrrstt
		$restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
		$extended_header_offset += 1;
		$thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']  = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
		$thisfile_id3v2['exthead']['flags']['restrictions']['textenc']  = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
		$thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
		$thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']   = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
		$thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']  = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
	}

	if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
		$info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
	}
}

$framedataoffset += $extended_header_offset;
$framedata = substr($framedata, $extended_header_offset);
} // end extended header

Locked