public void processFlags() { switch (MajorVersion) { default: case 2: //ID3v2.2 = ExtendedHeader Not Supported break; case 3: //ID3v2.3 = %x0000000 00000000 (x = CRCPresent) CRCPresent = ((RawData[4] & 0x80) == 0x80); PaddingSize = ID3Base.expand(RawData[6], RawData[7], RawData[8], RawData[9]); if (CRCPresent) { CRCData = ID3Base.expand(RawData[10], RawData[11], RawData[12], RawData[13]); } break; case 4: //ID3v2.4 = %0bcd0000 (b = TagIsUpdate, c = CRCPresent, d = TagRestrictions) int numOfFlags = (int)RawData[4]; for (int i = 0; i < numOfFlags; ++i) { TagIsUpdate = ((RawData[5 + i] & 0x40) == 0x40); CRCPresent = ((RawData[5 + i] & 0x20) == 0x20); TagRestrictions = ((RawData[5 + i] & 0x10) == 0x10); } int DataStartingPoint = 5 + numOfFlags; if (TagIsUpdate) { //Flag Data Length is $00 DataStartingPoint++; } if (CRCPresent) { //Flag Data Length is $05 DataStartingPoint++; CRCData = ID3Base.syncsafe(RawData[DataStartingPoint], RawData[DataStartingPoint + 1], RawData[DataStartingPoint + 2], RawData[DataStartingPoint + 3], RawData[DataStartingPoint + 4]); DataStartingPoint += 5; } if (TagRestrictions) { //Flag data length is $01 DataStartingPoint++; //Restrictions %ppqr rstt //p - Tag Size Restrictions byte p = (byte)(RawData[DataStartingPoint] & 0xC0); switch (p) { case 0x00: //0000 0000 [00] - No more than 128 frames and 1 MB total tag size. break; case 0x40: //0100 0000 [01] - No more than 64 frames and 128 KB total tag size. break; case 0x80: //1000 0000 [10] - No more than 32 frames and 40 KB total tag size. break; case 0xC0: //1100 0000 [11] - No more than 32 frames and 4 KB total tag size. break; } //q - Text encoding restrictions byte q = (byte)(RawData[DataStartingPoint] & 0x20); switch (q) { case 0x00: //0000 0000 [0] - No Restrictions break; case 0x20: //0010 0000 [1] - Strings are only encoded with ISO-8859-1 [ISO-8859-1] or UTF-8 [UTF-8]. break; } //r - Text fields size restrictions byte r = (byte)(RawData[DataStartingPoint] & 0x18); switch (r) { case 0x00: //0000 0000 [00] - No restrictions break; case 0x08: //0000 1000 [01] - No string is longer than 1024 characters. break; case 0x10: //0001 0000 [10] - No string is longer than 128 characters. break; case 0x18: //0001 1000 [11] - No string is longer than 30 characters. break; } //s - Image encoding restrictions byte s = (byte)(RawData[DataStartingPoint] & 0x04); switch (s) { case 0x00: //0000 0000 [0] - No restrictions break; case 0x04: //0000 0100 [1] - Images are encoded only with PNG [PNG] or JPEG [JFIF]. break; } //t - Image size restrictions byte t = (byte)(RawData[DataStartingPoint] & 0x03); switch (t) { case 0x00: //0000 0000 [00] - No restrictions break; case 0x01: //0000 0001 [01] - All images are 256x256 pixels or smaller. break; case 0x02: //0000 0010 [10] - All images are 64x64 pixels or smaller. break; case 0x03: //0000 0011 [11] - All images are exactly 64x64 pixels, unless required otherwise. break; } } break; } }
public ID3v2Tag(FileStream fs, long lngStartOffset) { //Store FileStreams current position long fsOriginalPosition = fs.Position; setDefaultValues(); Regex ProperFrameNamePattern = new Regex("[A-Z0-9]"); byte[] bytHeaderID3 = new byte[10]; fs.Position = lngStartOffset; fs.Read(bytHeaderID3, 0, 10); //ID3v2.X should start with "ID3" if ((char)bytHeaderID3[0] == 'I' && (char)bytHeaderID3[1] == 'D' && (char)bytHeaderID3[2] == '3') { Position = lngStartOffset; //Major and Minor Versions should NOT be 0xFF MajorVersion = (bytHeaderID3[3] != 0xFF) ? (int)bytHeaderID3[3] : 0; MinorVersion = (bytHeaderID3[4] != 0xFF) ? (int)bytHeaderID3[4] : 0; ProcessFlags(bytHeaderID3[5]); /* * ID3v2 Uses synchsafe integers, which skip the largest bit (farthest left) of each byte. * * See http://www.id3.org/id3v2.4.0-structure and http://id3lib.sourceforge.net/id3/id3v2.3.0.html#sec3.1 */ //Console.WriteLine("ID3v2.{0:D}.{1:D} Exists", tag.MajorVersion, tag.MinorVersion); //Max size for tag size = 2^28 = 268435456 bytes = 256MB; int.MaxValue = 2147483647 bytes = 2048MB TagSize = (uint)ID3Base.syncsafe(bytHeaderID3[6], bytHeaderID3[7], bytHeaderID3[8], bytHeaderID3[9]); //(int)((bytHeaderID3[6] << 21) | (bytHeaderID3[7] << 14) | (bytHeaderID3[8] << 7) | bytHeaderID3[9]); //Console.WriteLine("Len (Bytes): {0:D}", (ulong)( (bytHeaderID3[6] << 21) | (bytHeaderID3[7] << 14) | (bytHeaderID3[8] << 7) | bytHeaderID3[9] )); //Console.WriteLine("NUMBER: {0:D}", (ulong)( (0x00<<21) | (0x00<<14) | (0x02<<7) | (0x01) )); //Console.WriteLine("OUTPUT: {0:X}, {1:X}, {2:X}, {3:X}", bytHeaderID3[6], bytHeaderID3[7], bytHeaderID3[8], bytHeaderID3[9]); Exists = true; //Experimental - Extended Header Implementation ExtendedHeader.setID3Version(MajorVersion); if (ExtendedHeader.Exists && MajorVersion > 2) { byte[] EH_Size = new byte[4]; fs.Read(EH_Size, 0, 4); if (MajorVersion == 3) { //Extended Header size, excluding itself; only 6 or 10 bytes (6 if no CRC Data, 10 if CRC Data [4 bytes for CRC]) ExtendedHeader.size = (int)ID3Base.expand(EH_Size[0], EH_Size[1], EH_Size[2], EH_Size[3]); byte[] headerAndData = new byte[4 + ExtendedHeader.size]; fs.Position = lngStartOffset + 10; fs.Read(headerAndData, 0, headerAndData.Length); ExtendedHeader.setData(headerAndData); } else if (MajorVersion == 4) { //Whole Extended Header ExtendedHeader.size = ID3Base.syncsafe(EH_Size[0], EH_Size[1], EH_Size[2], EH_Size[3]); byte[] headerAndData = new byte[ExtendedHeader.size]; fs.Position = lngStartOffset + 10; fs.Read(headerAndData, 0, headerAndData.Length); ExtendedHeader.setData(headerAndData); } } //ID3v2.2.X uses 3 letter identifiers versus the 4 letter identifiers in 2.3.X and 2.4.X if (!Compression) { Console.WriteLine("ID3v2.{0:D} Detected...", MajorVersion); int totalFrameSize = 0; int AdditionalBytes = 10; if (FooterExists) { AdditionalBytes = 20; } while (!PaddingExists && fs.Position < TagSize + AdditionalBytes) //(tag.TagSize + 10) == End Position (+10 for Original Header, +20 For Original Header and Footer [if present]) { if (MajorVersion == 2) { byte[] TempHeader = new byte[6]; fs.Read(TempHeader, 0, 6); if (TempHeader[0] == 0x00 && TempHeader[1] == 0x00 && TempHeader[2] == 0x00) { PaddingExists = true; } else { Console.WriteLine("HEADER[{0}{1}{2}]", (char)TempHeader[0], (char)TempHeader[1], (char)TempHeader[2]); string FrameHeaderName = ((char)TempHeader[0]).ToString() + ((char)TempHeader[1]).ToString() + ((char)TempHeader[2]).ToString(); if (!ProperFrameNamePattern.IsMatch(FrameHeaderName)) { Console.WriteLine("Invalid ID3v2 Header"); Valid = false; break; } if (Frames.ContainsKey(FrameHeaderName)) { Frames[FrameHeaderName].Add(new ID3v2Frame(FrameHeaderName, MajorVersion)); } else { List <ID3v2Frame> FrameList = new List <ID3v2Frame>(); FrameList.Add(new ID3v2Frame(FrameHeaderName, MajorVersion)); Frames.Add(FrameHeaderName, FrameList); } int currentFrameIndex = Frames[FrameHeaderName].Count - 1; ID3v2Frame currentFrame = Frames[FrameHeaderName][currentFrameIndex]; currentFrame.getFrameFlags((byte)0x00, (byte)0x00, AllFramesUnsynced); //Frame Size currentFrame.FrameSize = (int)ID3Base.expand(TempHeader[3], TempHeader[4], TempHeader[5]); //(int)((TempHeader[3] << 16) | (TempHeader[4] << 8) | (TempHeader[5])); totalFrameSize += currentFrame.FrameSize + 6; //+6 for Frame Header //Set FrameData byte[] tempData = new byte[currentFrame.FrameSize]; fs.Read(tempData, 0, currentFrame.FrameSize); currentFrame.getFrameData(tempData); } } else { byte[] TempHeader = new byte[10]; fs.Read(TempHeader, 0, 10); //All Tags must be 4 Characters (characters capital A-Z and 0-9; not null) if (!FooterExists && (TempHeader[0] == 0x00 || TempHeader[1] == 0x00 || TempHeader[2] == 0x00 || TempHeader[3] == 0x00)) { //Nothing Here but Padding; Skip to the end of the tag //Footer and Padding are Mutually Exclusive PaddingExists = true; } else { Console.WriteLine("HEADER[{0}{1}{2}{3}]", (char)TempHeader[0], (char)TempHeader[1], (char)TempHeader[2], (char)TempHeader[3]); //Get the Frame Name string FrameHeaderName = ((char)TempHeader[0]).ToString() + ((char)TempHeader[1]).ToString() + ((char)TempHeader[2]).ToString() + ((char)TempHeader[3]).ToString(); if (!ProperFrameNamePattern.IsMatch(FrameHeaderName)) { Console.WriteLine("Invalid ID3v2 Header"); Valid = false; break; } if (Frames.ContainsKey(FrameHeaderName)) { Frames[FrameHeaderName].Add(new ID3v2Frame(FrameHeaderName, MajorVersion)); } else { List <ID3v2Frame> FrameList = new List <ID3v2Frame>(); FrameList.Add(new ID3v2Frame(FrameHeaderName, MajorVersion)); Frames.Add(FrameHeaderName, FrameList); } //Keep Track of which count of the Frame we are working on (some frames may be in the tag more than once) int currentFrameIndex = Frames[FrameHeaderName].Count - 1; ID3v2Frame currentFrame = Frames[FrameHeaderName][currentFrameIndex]; currentFrame.getFrameFlags(TempHeader[8], TempHeader[9], AllFramesUnsynced); //ID3v2.3 does not appear to use syncsafe numbers for frame sizes (2.4 does) bool unsync = currentFrame.FF_Unsynchronisation; if (MajorVersion > 3) { currentFrame.FrameSize = ID3Base.syncsafe(TempHeader[4], TempHeader[5], TempHeader[6], TempHeader[7]); } else { currentFrame.FrameSize = (int)ID3Base.expand(TempHeader[4], TempHeader[5], TempHeader[6], TempHeader[7]);//(int)((TempHeader[4] << 24) | (TempHeader[5] << 16) | (TempHeader[6] << 8) | (TempHeader[7])); } totalFrameSize += currentFrame.FrameSize + 10; //+10 for Frame Header //Set FrameData byte[] tempData = new byte[currentFrame.FrameSize]; fs.Read(tempData, 0, currentFrame.FrameSize); currentFrame.getFrameData(tempData); } } } Console.WriteLine("Padding Exsists: {0}", PaddingExists.ToString()); Console.WriteLine("TagSize: {0:D}, TotalFrameSize: {1:D}", TagSize, totalFrameSize); Console.WriteLine(); } } ProcessFrames(); fs.Position = fsOriginalPosition; }