/// <summary> /// Creates a new seekpoint. /// </summary> /// <param name="data"></param> public SeekPoint(byte[] data) { this.firstSampleNumber = BinaryDataHelper.GetUInt64(data, 0); this.byteOffset = BinaryDataHelper.GetUInt64(data, 8); this.numberOfSamples = BinaryDataHelper.GetUInt16(data, 16); ValidateIsPlaceholder(); }
/// <summary> /// Will write the data describing this metadata block to the given stream. /// </summary> /// <param name="targetStream">Stream to write the data to.</param> public override void WriteBlockData(Stream targetStream) { // This is where the header will come long headerPosition = targetStream.Position; // Moving along, we'll write the header last! targetStream.Seek(4, SeekOrigin.Current); // 32-bit picture type targetStream.Write(BinaryDataHelper.GetBytesUInt32((uint)this.pictureType), 0, 4); byte[] mimeTypeData = Encoding.ASCII.GetBytes(this.mimeType); // Length of the MIME type string (in bytes ...) targetStream.Write(BinaryDataHelper.GetBytesUInt32((uint)mimeTypeData.Length), 0, 4); // Only allows printable ascii characters (0x20 - 0x7e) for (int i = 0; i < mimeTypeData.Length; i++) { if (mimeTypeData[i] < 0x20 || mimeTypeData[i] > 0x7e) { // Make sure we write the text correctly as specified by the format. mimeTypeData[i] = 0x20; } } targetStream.Write(mimeTypeData, 0, mimeTypeData.Length); byte[] descriptionData = Encoding.UTF8.GetBytes(this.description); // Length of the description string (in bytes ...) targetStream.Write(BinaryDataHelper.GetBytesUInt32((uint)descriptionData.Length), 0, 4); // The description of the picture (in UTF-8) targetStream.Write(descriptionData, 0, descriptionData.Length); targetStream.Write(BinaryDataHelper.GetBytesUInt32((uint)this.width), 0, 4); targetStream.Write(BinaryDataHelper.GetBytesUInt32((uint)this.height), 0, 4); targetStream.Write(BinaryDataHelper.GetBytesUInt32((uint)this.colorDepth), 0, 4); targetStream.Write(BinaryDataHelper.GetBytesUInt32((uint)this.colors), 0, 4); // If --> is the mime type, the data contains the URL ... if (this.mimeType == "-->") { this.data = Encoding.ASCII.GetBytes(this.url); } if (this.data == null) { this.data = new byte[] { }; } targetStream.Write(BinaryDataHelper.GetBytesUInt32((uint)this.data.Length), 0, 4); targetStream.Write(this.data, 0, this.data.Length); // Writing the header, now we have the required information on the variable length fields CalculateMetadataBlockLength((uint)mimeTypeData.Length, (uint)descriptionData.Length, (uint)this.data.Length); long currentPosition = targetStream.Position; targetStream.Position = headerPosition; this.Header.WriteHeaderData(targetStream); targetStream.Position = currentPosition; }
/// <summary> /// Will write the data representing this CueSheet track index point to the given stream. /// </summary> /// <param name="targetStream"></param> public void WriteBlockData(Stream targetStream) { targetStream.Write(BinaryDataHelper.GetBytesUInt64(this.offset), 0, 8); targetStream.WriteByte(this.indexPointNumber); byte[] nullData = new byte[RESERVED_NULLDATA_LENGTH]; targetStream.Write(nullData, 0, nullData.Length); }
/// <summary> /// Will write the data describing this metadata block to the given stream. /// </summary> /// <param name="targetStream">Stream to write the data to.</param> public override void WriteBlockData(Stream targetStream) { this.Header.MetaDataBlockLength = 4 + (uint)this.applicationData.Length; this.Header.WriteHeaderData(targetStream); targetStream.Write(BinaryDataHelper.GetBytesUInt32(this.applicationID), 0, 4); targetStream.Write(this.applicationData, 0, this.applicationData.Length); }
/// <summary> /// Parses the given binary metadata to an ApplicationInfo block /// </summary> /// <param name="data"></param> public override void LoadBlockData(byte[] data) { this.applicationID = BinaryDataHelper.GetUInt32(data, 0); this.applicationData = new byte[data.Length - 4]; for (int i = 0; i < this.applicationData.Length; i++) { this.applicationData[i] = data[i + 4]; // + 4 because the first four bytes are the application ID! } }
/// <summary> /// Will write the data representing this header (as it is stored in the FLAC file) to the given stream. /// </summary> /// <param name="targetStream">The stream where the data will be written to.</param> public void WriteHeaderData(Stream targetStream) { byte data = this.isLastMetaDataBlock ? (byte)128 : (byte)0; // The 128 because the last metadata flag is the most significant bit set to 1 ... data += (byte)(this.typeID & 0x7F); // We make sure to chop off the last bit targetStream.WriteByte(data); // 24-bit metaDataBlockLength targetStream.Write(BinaryDataHelper.GetBytes((UInt64)this.metaDataBlockLength, 3), 0, 3); }
/// <summary> /// Creates a new SeekTable base on the provided binary data. /// </summary> /// <param name="data"></param> public override void LoadBlockData(byte[] data) { UInt32 numberOfSeekpoints; SeekPoint newSeekPoint; numberOfSeekpoints = this.Header.MetaDataBlockLength / SEEKPOINT_SIZE; for (int i = 0; i < numberOfSeekpoints; i++) { newSeekPoint = new SeekPoint(BinaryDataHelper.GetDataSubset(data, i * (int)SEEKPOINT_SIZE, (int)SEEKPOINT_SIZE)); // We should keep in mind that the placeholder seekpoints aren't actually added to the list but are kept only as a // count in this.SeekPoints.Placeholders this.SeekPoints.Add(newSeekPoint); } }
/// <summary> /// Will write the data describing this metadata block to the given stream. /// </summary> /// <param name="targetStream">Stream to write the data to.</param> public override void WriteBlockData(Stream targetStream) { uint totalLength = 0; long headerPosition = targetStream.Position; this.Header.WriteHeaderData(targetStream); // Write the vendor string (first write the length as a 32-bit uint and then the actual bytes byte[] vendorData = System.Text.Encoding.UTF8.GetBytes(this.vendor); byte[] number = BinaryDataHelper.GetBytesUInt32((uint)vendorData.Length); targetStream.Write(BinaryDataHelper.SwitchEndianness(number, 0, 4), 0, 4); targetStream.Write(vendorData, 0, vendorData.Length); totalLength += 4 + (uint)vendorData.Length; // Length of list of user comments (first a 32-bit uint, then the actual comments) number = BinaryDataHelper.GetBytesUInt32((uint)this.comments.Count); targetStream.Write(BinaryDataHelper.SwitchEndianness(number, 0, 4), 0, 4); totalLength += 4; foreach (var comment in this.comments) { foreach (var value in comment.Value) { string commentText = string.Format("{0}={1}", comment.Key, value); byte[] commentData = System.Text.Encoding.UTF8.GetBytes(commentText); number = BinaryDataHelper.GetBytesUInt32((uint)commentData.Length); targetStream.Write(BinaryDataHelper.SwitchEndianness(number, 0, 4), 0, 4); targetStream.Write(commentData, 0, commentData.Length); totalLength += 4 + (uint)commentData.Length; } } long endPosition = targetStream.Position; targetStream.Seek(headerPosition, SeekOrigin.Begin); this.Header.MetaDataBlockLength = totalLength; this.Header.WriteHeaderData(targetStream); targetStream.Seek(endPosition, SeekOrigin.Begin); // Note: FLAC does NOT have the framing bit for vorbis so we don't have to write this. }
/// <summary> /// Loads a new stream info block from the provided data. /// </summary> /// <param name="data"></param> public override void LoadBlockData(byte[] data) { //throw new Exception("The method or operation is not implemented."); // "All numbers are big-endian coded and unsigned". // 1: Minimum Block Size (first 16-bit) this.minimumBlockSize = BinaryDataHelper.GetUInt16(data, 0); this.maximumBlockSize = BinaryDataHelper.GetUInt16(data, 2); this.minimumFrameSize = BinaryDataHelper.GetUInt24(data, 4); this.maximumFrameSize = BinaryDataHelper.GetUInt24(data, 7); // Interpret 20 bits starting from byte 10 as a UInt this.sampleRateHz = (UInt32)BinaryDataHelper.GetUInt64(data, 10, 20); this.channels = (short)(BinaryDataHelper.GetUInt64(data, 12, 3, 4) + 1); this.bitsPerSample = (short)(BinaryDataHelper.GetUInt64(data, 12, 5, 7) + 1); this.samples = (long)BinaryDataHelper.GetUInt64(data, 13, 36, 4); this.md5Signature = new byte[16]; Array.Copy(data, 18, this.md5Signature, 0, 16); }
/// <summary> /// Will write the data describing this metadata block to the given stream. /// </summary> /// <param name="targetStream">Stream to write the data to.</param> public override void WriteBlockData(Stream targetStream) { if (this.Tracks.Count > 0) { var lastTrack = this.Tracks[this.Tracks.Count - 1]; if (!lastTrack.IsLeadOut) { throw new Exception(string.Format("CueSheet is invalid, last track (nr {0}) is not the lead-out track.", lastTrack.TrackNumber)); } } else { throw new Exception("CueSheet is invalid as it has no tracks, it must have at least one track (the lead-out track)."); } // TODO: this value in the header should also update when someone add/removes tracks or track index points ... this.Header.MetaDataBlockLength = CalculateMetaDataBlockLength(); this.Header.WriteHeaderData(targetStream); targetStream.Write(BinaryDataHelper.GetPaddedAsciiBytes(this.MediaCatalog, MEDIACATALOG_MAX_LENGTH), 0, MEDIACATALOG_MAX_LENGTH); targetStream.Write(BinaryDataHelper.GetBytesUInt64(this.LeadInSampleCount), 0, 8); byte isCDCueSheet = 0; if (this.isCDCueSheet) { isCDCueSheet = 0x80; // Most significant bit should be 1 } targetStream.WriteByte(isCDCueSheet); // Now we need to write 258 bytes of 0 data ("reserved") byte[] nullData = new byte[RESERVED_NULLDATA_LENGTH]; targetStream.Write(nullData, 0, nullData.Length); // The number of tracks i 1 byte in size ... targetStream.WriteByte(this.TrackCount); foreach (var track in Tracks) { track.WriteBlockData(targetStream); } }
/// <summary> /// Loads the picture data from a Metadata block. /// </summary> /// <param name="data"></param> public override void LoadBlockData(byte[] data) { // First 32-bit: picture type according to the ID3v2 APIC frame pictureType = (PictureType)(int)BinaryDataHelper.GetUInt32(data, 0); // Then the length of the MIME type text (32-bit) and the mime type int mimeTypeLength = (int)BinaryDataHelper.GetUInt32(data, 4); byte[] mimeData = BinaryDataHelper.GetDataSubset(data, 8, mimeTypeLength); this.mimeType = Encoding.ASCII.GetString(mimeData); int byteOffset = 8 + mimeTypeLength; // Then the description (in UTF-8) int descriptionLength = (int)BinaryDataHelper.GetUInt32(data, byteOffset); byte[] descriptionData = BinaryDataHelper.GetDataSubset(data, byteOffset + 4, descriptionLength); this.description = Encoding.UTF8.GetString(descriptionData); byteOffset += 4 + descriptionLength; this.width = BinaryDataHelper.GetUInt32(data, byteOffset); this.height = BinaryDataHelper.GetUInt32(data, byteOffset + 4); this.colorDepth = BinaryDataHelper.GetUInt32(data, byteOffset + 8); this.colors = BinaryDataHelper.GetUInt32(data, byteOffset + 12); byteOffset += 16; int dataLength = (int)BinaryDataHelper.GetUInt32(data, byteOffset); this.data = BinaryDataHelper.GetDataSubset(data, byteOffset + 4, dataLength); // According to the FLAC format, if the mimeType is the string -->, the data contains // a URL, pointing to the image. A URL should be ASCII encoded, but using UTF-8 seems // more sensible. if (mimeType == "-->") { this.url = Encoding.UTF8.GetString(this.data); } }
/// <summary> /// Parses the binary metadata from the flac file into a CueSheet object. /// </summary> /// <param name="data">The binary data from the flac file.</param> public override void LoadBlockData(byte[] data) { this.mediaCatalog = Encoding.ASCII.GetString(data, 0, 128).Trim(new char[] { '\0' }); this.leadInSampleCount = BinaryDataHelper.GetUInt64(data, 128); this.isCDCueSheet = BinaryDataHelper.GetBoolean(data, 136, 0); // We're skipping 7 bits + 258 bytes which is reserved null data byte trackCount = data[395]; if (trackCount > 100) { // Do we really need to throw an exception here? throw new Exception(string.Format("CueSheet has invalid track count {0}. Cannot be more than 100.", trackCount)); } int cueSheetTrackOffset = 396; for (int i = 0; i < trackCount; i++) { CueSheetTrack newTrack = new CueSheetTrack(data, cueSheetTrackOffset); cueSheetTrackOffset += 36 + (12 * newTrack.IndexPointCount); // 36 bytes for the cueSheetTrack and 12 bytes per index point ... this.Tracks.Add(newTrack); } }
/// <summary> /// When overridden in a derived class, will write the data describing this metadata block to the given stream. /// </summary> /// <param name="targetStream">Stream to write the data to.</param> public override void WriteBlockData(Stream targetStream) { this.Header.WriteHeaderData(targetStream); targetStream.Write(BinaryDataHelper.GetBytesUInt16(this.minimumBlockSize), 0, 2); targetStream.Write(BinaryDataHelper.GetBytesUInt16(this.maximumBlockSize), 0, 2); targetStream.Write(BinaryDataHelper.GetBytes(this.minimumFrameSize, 3), 0, 3); targetStream.Write(BinaryDataHelper.GetBytes(this.maximumFrameSize, 3), 0, 3); // next are 64 bits containing: // * sample rate in Hz (20-bits) // * number of channels (3 bits) // * bits per sample (5 bits) // * total samples per stream (36 bits) UInt64 combinedData = (UInt64)this.SampleRateHz << 44; combinedData = combinedData + ((UInt64)(this.channels - 1) << 41); combinedData = combinedData + ((UInt64)(this.bitsPerSample - 1) << 36); combinedData = combinedData + (UInt64)this.samples; targetStream.Write(BinaryDataHelper.GetBytes(combinedData, 8), 0, 8); targetStream.Write(this.md5Signature, 0, 16); }
/// <summary> /// Loads the Vorbis from a block of data. /// </summary> /// <param name="data"></param> public override void LoadBlockData(byte[] data) { UInt32 vendorLength = BinaryDataHelper.GetUInt32(BinaryDataHelper.SwitchEndianness(data, 0, 4), 0); this.vendor = Encoding.UTF8.GetString(BinaryDataHelper.GetDataSubset(data, 4, (int)vendorLength)); int startOfComments = 4 + (int)vendorLength; UInt32 userCommentListLength = BinaryDataHelper.GetUInt32(BinaryDataHelper.SwitchEndianness(data, startOfComments, 4), 0); // Start of comments actually four bytes further (first piece is the count of items in the list) startOfComments += 4; for (UInt32 i = 0; i < userCommentListLength; i++) { UInt32 commentLength = BinaryDataHelper.GetUInt32(BinaryDataHelper.SwitchEndianness(data, startOfComments, 4), 0); string comment = Encoding.UTF8.GetString(BinaryDataHelper.GetDataSubset(data, startOfComments + 4, (int)commentLength)); // We're moving on in the array ... startOfComments += 4 + (int)commentLength; AddComment(comment); } // All done, note that FLAC doesn't have the "fraiming bit" for vorbis ... }
/// <summary> /// Interprets the meta data block header. /// </summary> /// <param name="data"></param> protected void ParseData(byte[] data) { // Parses the 4 byte header data: // Bit 1: Last-metadata-block flag: '1' if this block is the last metadata block before the audio blocks, '0' otherwise. // Bit 2-8: Block Type, // 0 : STREAMINFO // 1 : PADDING // 2 : APPLICATION // 3 : SEEKTABLE // 4 : VORBIS_COMMENT // 5 : CUESHEET // 6 : PICTURE // 7-126 : reserved // 127 : invalid, to avoid confusion with a frame sync code // Next 3 bytes: Length (in bytes) of metadata to follow (does not include the size of the METADATA_BLOCK_HEADER) this.isLastMetaDataBlock = BinaryDataHelper.GetBoolean(data, 0, 0); typeID = data[0] & 0x7F; switch (typeID) { case 0: this.type = MetadataBlockType.StreamInfo; this.metaDataBlockLength = 34; break; case 1: this.type = MetadataBlockType.Padding; break; case 2: this.type = MetadataBlockType.Application; break; case 3: this.type = MetadataBlockType.Seektable; break; case 4: this.type = MetadataBlockType.VorbisComment; break; case 5: this.type = MetadataBlockType.CueSheet; break; case 6: this.type = MetadataBlockType.Picture; break; } if (typeID > 6 && typeID < 127) { this.type = MetadataBlockType.None; } else if (typeID >= 127) { this.type = MetadataBlockType.Invalid; } this.metaDataBlockLength = (BinaryDataHelper.GetUInt24(data, 1)); }
/// <summary> /// Creates a new Cue Sheet Track Index based on the binary data provided. /// </summary> /// <param name="data"></param> /// <param name="dataOffset">Where in the data array to start reading.</param> public CueSheetTrackIndex(byte[] data, int dataOffset) { this.offset = BinaryDataHelper.GetUInt64(data, dataOffset); this.indexPointNumber = (byte)BinaryDataHelper.GetUInt64(data, dataOffset + 8, 8); }
/// <summary> /// Will write the data representing this SeekPoint to the given stream. /// </summary> /// <param name="targetStream"></param> public void WriteData(Stream targetStream) { targetStream.Write(BinaryDataHelper.GetBytesUInt64(this.firstSampleNumber), 0, 8); targetStream.Write(BinaryDataHelper.GetBytesUInt64(this.byteOffset), 0, 8); targetStream.Write(BinaryDataHelper.GetBytesUInt16(this.numberOfSamples), 0, 2); }