/// <summary> /// Load header from the stream. /// </summary> /// <param name="stream">Stream to load header</param> public void Deserialize(Stream stream) { BinaryReader reader = new BinaryReader(stream); byte[] idTag = new byte[3]; // Read the tag identifier reader.Read(idTag, 0, 3); // Compare the readed tag if (Memory.Compare(_id3, idTag, 3) == false) { throw new TagNotFoundException("ID3v2 tag identifier was not found"); } // Get the id3v2 version byte _id3Version = reader.ReadByte(); if (_id3Version == 0xff) { throw new InvalidTagException("Corrupt header, invalid ID3v2 version."); } // Get the id3v2 revision byte _id3Revision = reader.ReadByte(); if (_id3Revision == 0xff) { throw new InvalidTagException("Corrupt header, invalid ID3v2 revision."); } // Get the id3v2 flag byte, only read what I understand _id3Flags = (byte)(0xf0 & reader.ReadByte()); // Get the id3v2 size, swap and unsync the integer _id3RawSize = Swap.UInt32(Sync.UnsafeBigEndian(reader.ReadUInt32())); if (_id3RawSize == 0) { throw new InvalidTagException("Corrupt header, tag size can't be zero."); } }
/// <summary> /// Create a frame depending on the tag form its binary representation. /// </summary> /// <param name="frameId">type of frame</param> /// <param name="flags">frame flags</param> /// <param name="frame">binary frame</param> /// <returns>Frame of tag type</returns> public FrameBase Build(string frameId, ushort flags, byte[] buffer) { // Build a frame var frame = FrameFactory.Build(frameId); SetFlags(frame, flags); uint index = 0; uint size = (uint)buffer.Length; Stream stream = new MemoryStream(buffer, false); var reader = new BinaryReader(stream); if (GetGrouping(flags) == true) { frame.Group = reader.ReadByte(); index++; } if (frame.Compression == true) { switch (Version) { case 3: { size = Swap.UInt32(reader.ReadUInt32()); break; } case 4: { size = Swap.UInt32(Sync.UnsafeBigEndian(reader.ReadUInt32())); break; } default: { throw new NotImplementedException("ID3v2 Version " + Version + " is not supported."); } } index = 0; stream = new InflaterInputStream(stream); } if (frame.Encryption == true) { throw new NotImplementedException("Encryption is not implemented, consequently it is not supported."); } if (frame.Unsynchronisation == true) { var memoryStream = new MemoryStream(); size = Sync.Unsafe(stream, memoryStream, size); index = 0; memoryStream.Seek(0, SeekOrigin.Begin); stream = memoryStream; } byte[] frameBuffer = new byte[size - index]; stream.Read(frameBuffer, 0, (int)(size - index)); frame.Parse(frameBuffer); return(frame); }
/// <summary> /// Save corresponding footer into the stream. /// </summary> /// <param name="stream">Stream to save header</param> public void SerializeFooter(Stream stream) { // open a writer on the underlying stream but don't use 'using' // or Dispose will close the underlying stream at the end BinaryWriter writer = new BinaryWriter(stream); //TODO: Validate version and revision we support writer.Write(_3di); // ID3v2/file footer identifier; ID3 backwards. writer.Write(_id3Version); // ID3v2 version, e.g. 3 or 4 writer.Write(_id3Revision); // ID3v2 revision, e.g. 0 writer.Write(_id3Flags); // ID3v2 flags writer.Write(Swap.UInt32(Sync.Safe(_id3RawSize))); }
/// <summary> /// Load the ID3 extended header from a stream /// </summary> /// <param name="stream">Binary stream containing a ID3 extended header</param> public void Deserialize(Stream stream) { BinaryReader reader = new BinaryReader(stream); _size = Swap.UInt32(Sync.UnsafeBigEndian(reader.ReadUInt32())); if (_size < 6) { throw new InvalidFrameException("Corrupt id3 extended header."); } // TODO: implement the extended header, copy for now since it's optional _extendedHeader = new Byte[_size]; stream.Read(_extendedHeader, 0, (int)_size); }
/// <summary> /// Load the ID3 extended header from a stream /// </summary> /// <param name="stream">Binary stream containing a ID3 extended header</param> public void Deserialize([NotNull] Stream stream) { if (stream == null) { throw new ArgumentNullException("stream"); } using (var reader = new BinaryReader(stream, Encoding.UTF8, true)) Size = Swap.UInt32(Sync.UnsafeBigEndian(reader.ReadUInt32())); if (Size < 6) { throw new InvalidFrameException("Corrupt id3 extended header."); } // TODO: implement the extended header, copy for now since it's optional _extendedHeader = new byte[Size]; stream.Read(_extendedHeader, 0, (int)Size); }
/// <summary> /// Build a binary data frame form the frame object. /// </summary> /// <param name="frame">ID3 Frame</param> /// <returns>binary frame representation</returns> public byte[] Make(FrameBase frame, out ushort flags) { flags = GetFlags(frame); var buffer = frame.Make(); var memoryStream = new MemoryStream(); var writer = new BinaryWriter(memoryStream); if (frame.Group.HasValue == true) { writer.Write((byte)frame.Group); } if (frame.Compression == true) { switch (Version) { case 3: { writer.Write(Swap.Int32(buffer.Length)); break; } case 4: { writer.Write(Sync.UnsafeBigEndian(Swap.UInt32((uint)buffer.Length))); break; } default: { throw new NotImplementedException("ID3v2 Version " + Version + " is not supported."); } } var buf = new byte[2048]; var deflater = new Deflater(Deflater.BEST_COMPRESSION); deflater.SetInput(buffer, 0, buffer.Length); deflater.Finish(); while (!deflater.IsNeedingInput) { int len = deflater.Deflate(buf, 0, buf.Length); if (len <= 0) { break; } memoryStream.Write(buf, 0, len); } if (!deflater.IsNeedingInput) { //TODO: Skip and remove invalid frames. throw new InvalidFrameException("Can't decompress frame '" + frame.FrameId + "' missing data"); } } else { memoryStream.Write(buffer, 0, buffer.Length); } if (frame.Encryption == true) { //TODO: Encryption throw new NotImplementedException("Encryption is not implemented, consequently it is not supported."); } if (frame.Unsynchronisation == true) { MemoryStream synchStream = new MemoryStream(); Sync.Unsafe(memoryStream, synchStream, (uint)memoryStream.Position); memoryStream = synchStream; } return(memoryStream.ToArray()); }
internal FrameBase Build([NotNull] string frameId, ushort flags, [NotNull] byte[] buffer) { // Build a frame var frame = FrameFactory.Build(frameId); SetFlags(frame, flags); var index = 0; var size = (uint)buffer.Length; Stream stream = new MemoryStream(buffer, false); var streamsToClose = new List <Stream>(3) { stream }; try { using (var reader = new BinaryReader(stream, Encoding.UTF8, false)) { if (GetGrouping(flags)) { frame.Group = reader.ReadByte(); index++; } if (frame.Compression) { switch (_version) { case 3: size = Swap.UInt32(reader.ReadUInt32()); break; case 4: size = Swap.UInt32(Sync.UnsafeBigEndian(reader.ReadUInt32())); break; default: throw new NotImplementedException($"ID3v2 Version {_version} is not supported."); } index = 0; stream = new InflaterInputStream(stream); streamsToClose.Add(stream); } if (frame.Encryption) { throw new NotImplementedException( "Encryption is not implemented, consequently it is not supported."); } if (frame.Unsynchronisation) { var memoryStream = new MemoryStream(); streamsToClose.Add(memoryStream); size = Sync.Unsafe(stream, memoryStream, size); index = 0; memoryStream.Seek(0, SeekOrigin.Begin); stream = memoryStream; } var frameBuffer = new byte[size - index]; stream.Read(frameBuffer, 0, (int)(size - index)); frame.Parse(frameBuffer); return(frame); } } finally { foreach (var streamToClose in streamsToClose) { streamToClose.Close(); } } }
internal byte[] Make([NotNull] FrameBase frame, out ushort flags) { flags = GetFlags(frame); var buffer = frame.Make(); var memoryStream = new MemoryStream(); var streamsToClose = new List <Stream>(2) { memoryStream }; try { using (var writer = new BinaryWriter(memoryStream, Encoding.UTF8, true)) { if (frame.Group.HasValue) { writer.Write((byte)frame.Group); } if (frame.Compression) { switch (_version) { case 3: writer.Write(Swap.Int32(buffer.Length)); break; case 4: writer.Write(Sync.UnsafeBigEndian(Swap.UInt32((uint)buffer.Length))); break; default: throw new NotImplementedException($"ID3v2 Version {_version} is not supported."); } var buf = new byte[2048]; var deflater = new Deflater(Deflater.BEST_COMPRESSION); deflater.SetInput(buffer, 0, buffer.Length); deflater.Finish(); while (!deflater.IsNeedingInput) { var len = deflater.Deflate(buf, 0, buf.Length); if (len <= 0) { break; } memoryStream.Write(buf, 0, len); } //TODO: Skip and remove invalid frames. if (!deflater.IsNeedingInput) { throw new InvalidFrameException($"Can't decompress frame '{frame.FrameId}' missing data"); } } else { memoryStream.Write(buffer, 0, buffer.Length); } //TODO: Encryption if (frame.Encryption) { throw new NotImplementedException( "Encryption is not implemented, consequently it is not supported."); } if (frame.Unsynchronisation) { var synchStream = new MemoryStream(); streamsToClose.Add(synchStream); Sync.Unsafe(memoryStream, synchStream, (uint)memoryStream.Position); memoryStream = synchStream; } return(memoryStream.ToArray()); } } finally { foreach (var streamToClose in streamsToClose) { streamToClose.Close(); } } }
/// <summary> /// Load the ID3v2 frames to a binary stream /// </summary> /// <param name="stream">Bynary stream holding the ID3 Tag</param> /// <returns>Model keeping the ID3 Tag structure</returns> public static FrameModel Deserialize(Stream stream) { FrameModel frameModel = new FrameModel(); frameModel.Header.Deserialize(stream); // load the ID3v2 header if (frameModel.Header.Version != 3 & frameModel.Header.Version != 4) { throw new NotImplementedException("ID3v2 Version " + frameModel.Header.Version + " is not supported."); } uint id3TagSize = frameModel.Header.TagSize; if (frameModel.Header.Unsync == true) { MemoryStream memory = new MemoryStream(); id3TagSize -= Sync.Unsafe(stream, memory, id3TagSize); stream = memory; // This is now the stream if (id3TagSize <= 0) { throw new InvalidTagException("Data is missing after the header."); } } uint rawSize; // load the extended header if (frameModel.Header.ExtendedHeader == true) { frameModel.ExtendedHeader.Deserialize(stream); rawSize = id3TagSize - frameModel.ExtendedHeader.Size; if (id3TagSize <= 0) { throw new InvalidTagException("Data is missing after the extended header."); } } else { rawSize = id3TagSize; } // Read the frames if (rawSize <= 0) { throw new InvalidTagException("No frames are present in the Tag, there must be at least one present."); } BinaryReader reader = new BinaryReader(stream); // Load the tag frames uint index = 0; FrameHelper frameHelper = new FrameHelper(frameModel.Header); // repeat while there is at least one complete frame available, 10 is the minimum size of a valid frame // but what happens when there's only, say, 5 bytes of padding? // we need to read a single byte to inspect for padding, then if not, read a whole tag. while (index < rawSize) { byte[] frameId = new byte[4]; reader.Read(frameId, 0, 1); if (frameId[0] == 0) { // We reached the padding area between the frames and the end of the tag, // signified by a zero byte where the frame name should be. // we could double check we actually know what's going on // and check the padding goes exactly to the end of the id3 tag // but in fact it doesn't give us any benefit. // // one of the following cases must apply: // // 1) if the id3 tag specifies more bytes than the frames use up, // and that space is exactly filled with zeros to the first audio frame, // it complies with the standard and everything is happy. // // 2) if the id3 tag specifies more bytes than the frames use up, // and that space isn't completely filled with zeros, // we assume the software that generated the tag // forgot to zero-fill it properly. // // 3) if the zero padding extends past the start of the id3 tag, // we assume the audio payload starts with skippable stuff too. // // 4) if the audio payload doesn't start with a valid mpeg audio frame header, // (vbr headers have valid mpeg audio frame headers) // we assume there's a tag in a format we don't recognise. // It still has to comply with the mpeg sync rules, // so we will have to use that to find the start of the audio. // // in all cases, we read the specified length of the id3 tag // and let the higher-level processing inspect the audio payload // to decide what is audio, what is extra padding, // and what is unrecognised (non-id3) tags. // how much does the tag size say should be left? frameModel.Header.PaddingSize = rawSize - index; //// advance the stream past any zero bytes, //// and verify the real measured size against that specified in the tag //uint observed = SeekEndOfPadding(src) + 1; //if( frameModel.Header.PaddingSize != observed ) // throw new InvalidPaddingException(observed, frameModel.Header.PaddingSize); // advance the stream to the specified end of the tag // this skips any non-zero rubbish in the padding without looking at it. stream.Seek(frameModel.Header.PaddingSize - 1, SeekOrigin.Current); break; } if (index + 10 > rawSize) { // 10 is the minimum size of a valid frame; // we read one already, if less than 9 chars left it's an error. throw new InvalidTagException("Tag is corrupt, must be formed of complete frames."); } // read another 3 chars reader.Read(frameId, 1, 3); index += 4; // have read 4 bytes //TODO: Validate key valid ranges uint frameSize = Swap.UInt32(reader.ReadUInt32()); index += 4; // have read 4 bytes // ID3v4 now has syncsafe sizes if (frameModel.Header.Version == 4) { Sync.Unsafe(frameSize); } // The size of the frame can't be larger than the avaliable space if (frameSize > rawSize - index) { throw new InvalidFrameException("A frame is corrupt, it can't be larger than the avaliable space remaining."); } ushort flags = Swap.UInt16(reader.ReadUInt16()); index += 2; // read 2 bytes byte[] frameData = new byte[frameSize]; reader.Read(frameData, 0, (int)frameSize); index += frameSize; // read more bytes frameModel.Add(frameHelper.Build(UTF8Encoding.UTF8.GetString(frameId, 0, 4), flags, frameData)); } return(frameModel); }
///// <summary> ///// find the first non-zero character in the stream ///// then back up one, so it will be the first char that gets read ///// and the stream position is pointing to the start of the non-zero (e.g. audio) data ///// </summary> ///// <param name="stream"></param> ///// <returns></returns> //static private uint SeekEndOfPadding( Stream stream ) //{ // uint index = 0; // while( stream.ReadByte() == 0 ) // { // ++index; // } // stream.Seek(-1, SeekOrigin.Current); // return index; //} /// <summary> /// Save the ID3v2 frames to a binary stream /// </summary> /// <param name="frameModel">Model keeping the ID3 Tag structure</param> /// <param name="stream">Stream keeping the ID3 Tag</param> public static void Serialize(FrameModel frameModel, Stream stream) { if (frameModel.Count <= 0) { throw new InvalidTagException("Can't serialize a ID3v2 tag without any frames, there must be at least one present."); } MemoryStream memory = new MemoryStream(); BinaryWriter writer = new BinaryWriter(memory); FrameHelper frameHelper = new FrameHelper(frameModel.Header); // Write the frames in binary format foreach (IFrame frameBase in frameModel) { //TODO: Do validations on tag name correctness byte[] frameId = new byte[4]; UTF8Encoding.UTF8.GetBytes(frameBase.FrameId, 0, 4, frameId, 0); writer.Write(frameId); // Write the 4 byte text tag byte[] frame = frameHelper.Make(frameBase); uint frameSize = (uint)frame.Length; if (frameModel.Header.Version == 4) { Sync.Safe(frameSize); } writer.Write(Swap.UInt32(frameSize)); writer.Write(Swap.UInt16(frameBase.Flags)); writer.Write(frame); } uint id3TagSize = (uint)memory.Position; // Skip the header 10 bytes for now, we will come back and write the Header // with the correct size once have the tag size + padding stream.Seek(10, SeekOrigin.Begin); // TODO: Add extended header handling if (frameModel.Header.Unsync == true) { id3TagSize += Sync.Safe(memory, stream, id3TagSize); } else { memory.WriteTo(stream); } // update the TagSize stored in the tagModel frameModel.Header.TagSize = id3TagSize; // next write the padding of zeros, if any if (frameModel.Header.Padding) { byte[] bytes = new byte[frameModel.Header.PaddingSize]; stream.Write(bytes, 0, (int)frameModel.Header.PaddingSize); } // next write the footer, if any if (frameModel.Header.Footer) { frameModel.Header.SerializeFooter(stream); } // Now seek back to the start and write the header long position = stream.Position; stream.Seek(0, SeekOrigin.Begin); frameModel.Header.Serialize(stream); // reset position to the end of the tag stream.Position = position; }
/// <summary> /// Save the ID3v2 frames to a binary stream /// </summary> /// <param name="frameModel">Model keeping the ID3 Tag structure</param> /// <param name="stream">Stream keeping the ID3 Tag</param> public static void Serialize([NotNull] TagModel frameModel, [NotNull] Stream stream) { if (frameModel.Count <= 0) { throw new InvalidTagException("Can't serialize a ID3v2 tag without any frames, there must be at least one present."); } using (var memory = new MemoryStream()) using (var writer = new BinaryWriter(memory, Encoding.UTF8, true)) { var frameHelper = new FrameHelper(frameModel.Header); // Write the frames in binary format foreach (var frame in frameModel) { //TODO: Do validations on tag name correctness var frameId = new byte[4]; Encoding.UTF8.GetBytes(frame.FrameId, 0, 4, frameId, 0); writer.Write(frameId); // Write the 4 byte text tag var buffer = frameHelper.Make(frame, out var flags); var frameSize = (uint)buffer.Length; if (frameModel.Header.Version == 4) { frameSize = Sync.Safe(frameSize); } writer.Write(Swap.UInt32(frameSize)); writer.Write(Swap.UInt16(flags)); writer.Write(buffer); } var id3TagSize = (uint)memory.Position; // Skip the header 10 bytes for now, we will come back and write the Header // with the correct size once have the tag size + padding stream.Seek(10, SeekOrigin.Begin); // TODO: Add extended header handling if (frameModel.Header.Unsync) { id3TagSize += Sync.Safe(memory, stream, id3TagSize); } else { memory.WriteTo(stream); } // update the TagSize stored in the tagModel frameModel.Header.TagSize = id3TagSize; // If padding + tag size is too big, shrink the padding (rather than throwing an exception) frameModel.Header.PaddingSize = Math.Min(frameModel.Header.PaddingSize, 0x10000000 - id3TagSize); // next write the padding of zeros, if any if (frameModel.Header.Padding) { for (var i = 0; i < frameModel.Header.PaddingSize; i++) { stream.WriteByte(0); } } // next write the footer, if any if (frameModel.Header.Footer) { frameModel.Header.SerializeFooter(stream); } // Now seek back to the start and write the header var position = stream.Position; stream.Seek(0, SeekOrigin.Begin); frameModel.Header.Serialize(stream); // reset position to the end of the tag stream.Position = position; } }
/// <summary> /// Create a frame depending on the tag form its binary representation. /// </summary> /// <param name="frameId">type of frame</param> /// <param name="flags">frame flags</param> /// <param name="frame">binary frame</param> /// <returns>Frame of tag type</returns> public IFrame Build(string frameId, ushort options, byte[] frame) { // Build a frame IFrame frameBase = FrameFactory.Build(frameId); frameBase.Flags = options; flagHandler.Flags = options; uint index = 0; uint size = (uint)frame.Length; Stream stream = new MemoryStream(frame, false); BinaryReader reader = new BinaryReader(stream); if (flagHandler.Grouping == true) { //TODO: Implement grouping when watching grass grow stops beeing intresting. /* The byte readed here is the group, we are skiping it for now*/ reader.ReadByte(); index++; } if (flagHandler.Compression == true) { switch (flagHandler.Version) { case 3: { size = Swap.UInt32(reader.ReadUInt32()); break; } case 4: { size = Swap.UInt32(Sync.UnsafeBigEndian(reader.ReadUInt32())); break; } default: { throw new NotImplementedException("ID3v2 Version " + flagHandler.Version + " is not supported."); } } index = 0; stream = new InflaterInputStream(stream); } if (flagHandler.Encryption == true) { //TODO: Encryption throw new NotImplementedException("Encryption is not implemented, consequently it is not supported."); } if (flagHandler.Unsynchronisation == true) { Stream memStream = new MemoryStream(); size = Sync.Unsafe(stream, memStream, size); index = 0; stream = memStream; } byte[] frameBuffer = new byte[size - index]; stream.Read(frameBuffer, 0, (int)(size - index)); frameBase.Parse(frameBuffer); return(frameBase); }
/// <summary> /// Build a binary data frame form the frame object. /// </summary> /// <param name="frameBase">ID3 Frame</param> /// <returns>binary frame representation</returns> public byte[] Make(IFrame frameBase) { flagHandler.Flags = frameBase.Flags; byte[] frame = frameBase.Make(); MemoryStream memoryStream = new MemoryStream(); BinaryWriter writer = new BinaryWriter(memoryStream); if (flagHandler.Grouping == true) { //TODO: Do grouping some place in the universe byte _group = 0; writer.Write(_group); } if (flagHandler.Compression == true) { switch (flagHandler.Version) { case 3: { writer.Write(Swap.Int32(frame.Length)); break; } case 4: { writer.Write(Sync.UnsafeBigEndian(Swap.UInt32((uint)frame.Length))); break; } default: { throw new NotImplementedException("ID3v2 Version " + flagHandler.Version + " is not supported."); } } byte[] buf = new byte[2048]; Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION); deflater.SetInput(frame, 0, frame.Length); deflater.Finish(); while (!deflater.IsNeedingInput) { int len = deflater.Deflate(buf, 0, buf.Length); if (len <= 0) { break; } memoryStream.Write(buf, 0, len); } if (!deflater.IsNeedingInput) { //TODO: Skip and remove invalid frames. throw new InvalidFrameException("Can't decompress frame '" + frameBase.FrameId + "' missing data"); } } else { memoryStream.Write(frame, 0, frame.Length); } if (flagHandler.Encryption == true) { //TODO: Encryption throw new NotImplementedException("Encryption is not implemented, consequently it is not supported."); } if (flagHandler.Unsynchronisation == true) { MemoryStream synchStream = new MemoryStream(); Sync.Unsafe(memoryStream, synchStream, (uint)memoryStream.Position); memoryStream = synchStream; } return(memoryStream.ToArray()); }