public APEv2Tag(FileStream fs, ID3v1Tag id3v1, Lyrics3Tag lyrics3, bool afterAudioData) { Appended = false; Exists = false; Valid = false; long lngOffset = 32; //Size of footer tag of APEv2 if (id3v1.Exists) { lngOffset += id3v1.TagSize; //128 Bytes for ID3v1 } if (lyrics3.Exists) { lngOffset += lyrics3.TagSize; } RawFooter = new byte[32]; fs.Position = fs.Length - lngOffset; fs.Read(RawFooter, 0, 32); if (Encoding.ASCII.GetString(RawFooter, 0, 8) == "APETAGEX") { Exists = true; Appended = true; //Version uint v = ID3Base.expand(RawFooter[11], RawFooter[10], RawFooter[9], RawFooter[8]); TagSize = ID3Base.expand(RawFooter[15], RawFooter[14], RawFooter[13], RawFooter[12]); uint numOfItems = ID3Base.expand(RawFooter[19], RawFooter[18], RawFooter[17], RawFooter[16]); //Detect Header fs.Position = fs.Length - (lngOffset + TagSize); RawHeader = new byte[32]; fs.Read(RawHeader, 0, 32); if (Encoding.ASCII.GetString(RawHeader, 0, 8) == "APETAGEX") { //WERE GOOD ProcessFlags(); } } }
public VBRHeader(FileStream fs, long startOffset) { fs.Position = startOffset; byte[] inputheader = new byte[8]; //4 for Xing or Info, 4 for flags fs.Read(inputheader, 0, 8); // If it's a variable bitrate MP3, the first 4 bytes will read 'Xing' // since they're the ones who added variable bitrate-edness to MP3s string HeaderType = Encoding.ASCII.GetString(inputheader, 0, 4); if (HeaderType == "Xing" || HeaderType == "Info") //(char)inputheader[0] == 'X' && (char)inputheader[1] == 'i' && (char)inputheader[2] == 'n' && (char)inputheader[3] == 'g') { VBRType = HeaderType; Exists = true; Position = startOffset; int flags = (int)ID3Base.expand(inputheader[4], inputheader[5], inputheader[6], inputheader[7]);//(int)(((inputheader[4] & 255) << 24) | ((inputheader[5] & 255) << 16) | ((inputheader[6] & 255) << 8) | ((inputheader[7] & 255))); /** * Flags: * Frames * Bytes * TOC * Scale */ framesFlag = ((flags & 0x01) == 0x01); // total bit stream frames from Xing header data bytesFlag = ((flags & 0x02) == 0x02); // total bit stream bytes from Xing header data tocFlag = ((flags & 0x04) == 0x04); vbrScaleFlag = ((flags & 0x08) == 0x08); // encoded vbr scale from Xing header data int byteCount = 0; if (framesFlag) { byteCount += 4; } if (bytesFlag) { byteCount += 4; } if (tocFlag) { byteCount += 100; } if (vbrScaleFlag) { byteCount += 4; } byte[] inputheaderData = new byte[byteCount]; fs.Read(inputheaderData, 0, byteCount); int pos = 0; if (framesFlag) { intVFrames = (int)ID3Base.expand(inputheaderData[pos], inputheaderData[pos + 1], inputheaderData[pos + 2], inputheaderData[pos + 3]);//(int)(((inputheader[8] & 255) << 24) | ((inputheader[9] & 255) << 16) | ((inputheader[10] & 255) << 8) | ((inputheader[11] & 255))); pos += 4; } else { intVFrames = -1; } if (bytesFlag) { file_bytes = (int)ID3Base.expand(inputheaderData[pos], inputheaderData[pos + 1], inputheaderData[pos + 2], inputheaderData[pos + 3]); pos += 4; } if (tocFlag) { TOC = new byte[100]; for (int i = 0; i < 100; ++i) { TOC[i] = inputheaderData[pos]; pos++; } } int vbrScale = -1; if (vbrScaleFlag) { vbrScale = (int)ID3Base.expand(inputheaderData[pos], inputheaderData[pos + 1], inputheaderData[pos + 2], inputheaderData[pos + 3]); pos += 4; } HeaderSize = pos + 8; //+8 for original 8 bytes ["Xing" + Flags] if (HeaderType == "Xing") { //VBR Indeed bVBR = true; SeekPoint(100); } else { //Header is 'Info' which means CBR bVBR = false; } } }
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; }