protected override bool read(BinaryReader source, ReadTagParams readTagParams) { bool result = true; ResetData(); readHeader(source); if (toc.ContainsKey(TOC_CONTENT_TAGS)) { readTags(source, toc[TOC_CONTENT_TAGS].Item1, toc[TOC_CONTENT_TAGS].Item2, readTagParams); } if (toc.ContainsKey(TOC_COVER_ART)) { if (readTagParams.ReadPictures) { readCover(source, toc[TOC_COVER_ART].Item1, PictureInfo.PIC_TYPE.Generic); } else { addPictureToken(PictureInfo.PIC_TYPE.Generic); } } readChapters(source, toc[TOC_AUDIO].Item1, toc[TOC_AUDIO].Item2); return(result); }
public static void FromStream(Stream source, MetaDataIO meta, ReadTagParams readTagParams, uint chunkSize) { long position = source.Position; long initialPos = position; string key, value; int size; byte[] data = new byte[chunkSize]; while (source.Position < initialPos + chunkSize - 4) // 4 being the "INFO" purpose that belongs to the chunk { // Key source.Read(data, 0, 4); key = Utils.Latin1Encoding.GetString(data, 0, 4); // Size source.Read(data, 0, 4); size = StreamUtils.DecodeInt32(data); if (size > 0) { source.Read(data, 0, size); // Manage parasite zeroes at the end of data if (source.ReadByte() != 0) { source.Seek(-1, SeekOrigin.Current); } value = Utils.Latin1Encoding.GetString(data, 0, size); meta.SetMetaField("info." + key, Utils.StripEndingZeroChars(value), readTagParams.ReadAllMetaFrames); } } }
public static void FromStream(Stream source, MetaDataIO meta, ReadTagParams readTagParams, UInt32 chunkSize) { var position = source.Position; var initialPos = position; String key, value; Int32 size; var data = new Byte[256]; while (source.Position < initialPos + chunkSize - 4) // 4 being the "INFO" purpose that belongs to the chunk { // Key source.Read(data, 0, 4); key = Utils.Latin1Encoding.GetString(data, 0, 4); // Size source.Read(data, 0, 4); size = StreamUtils.DecodeInt32(data); // Value value = StreamUtils.ReadNullTerminatedString(source, Utils.Latin1Encoding); if (value.Length > 0) { meta.SetMetaField("info." + key, value, readTagParams.ReadAllMetaFrames); } position = source.Position; } }
public void ReadPicture(Stream s, ReadTagParams readTagParams) { int picturePosition; long initPosition = s.Position; VorbisMetaDataBlockPicture block = ReadMetadataBlockPicture(s); if (block.picType.Equals(PictureInfo.PIC_TYPE.Unsupported)) { addPictureToken(getImplementedTagType(), (byte)block.nativePicCode); picturePosition = takePicturePosition(getImplementedTagType(), (byte)block.nativePicCode); } else { addPictureToken(block.picType); picturePosition = takePicturePosition(block.picType); } if (readTagParams.ReadPictures) { s.Seek(initPosition + block.picDataOffset, SeekOrigin.Begin); PictureInfo picInfo = PictureInfo.fromBinaryData(s, block.picDataLength, block.picType, getImplementedTagType(), block.nativePicCode, picturePosition); picInfo.Description = block.description; tagData.Pictures.Add(picInfo); if (!tagExists) { tagExists = true; } } }
// NB : This only works if writeVorbisTag is called _before_ writePictures, since tagData fusion is done by vorbisTag.Write public bool Write(BinaryReader r, BinaryWriter w, TagData tag) { // Read all the fields in the existing tag (including unsupported fields) ReadTagParams readTagParams = new ReadTagParams(true, true); readTagParams.PrepareForWriting = true; bool tagExists = Read(r, readTagParams); // Save a snapshot of the initial embedded pictures for processing purposes existingPictureIndex = 0; targetPictureIndex = 0; initialPictures = vorbisTag.EmbeddedPictures; // Prepare picture data with freshly read vorbisTag TagData dataToWrite = new TagData(); dataToWrite.Pictures = vorbisTag.EmbeddedPictures; dataToWrite.IntegrateValues(tag, true, false); // Merge existing information + new tag information except additional fields which will be merged by VorbisComment adjustPictureZones(dataToWrite.Pictures); FileSurgeon surgeon = new FileSurgeon(null, null, MetaDataIOFactory.TAG_NATIVE, TO_BUILTIN); surgeon.RewriteZones(w, new WriteDelegate(write), zones, dataToWrite, tagExists); // Set the 'isLast' bit on the actual last block w.BaseStream.Seek(latestBlockOffset, SeekOrigin.Begin); w.Write((byte)(latestBlockType | FLAG_LAST_METADATA_BLOCK)); return(true); }
public bool Read(BinaryReader source, ReadTagParams readTagParams) { bool result = false; BufferedBinaryReader reader = new BufferedBinaryReader(source.BaseStream); if (readTagParams.ReadTag && null == vorbisTag) { vorbisTag = new VorbisTag(true, true, true); } info.Reset(); if (getInfo(reader, info, readTagParams)) { if (contents.Equals(CONTENTS_VORBIS)) { channelsArrangement = getArrangementFromCode(info.VorbisParameters.ChannelMode); sampleRate = info.VorbisParameters.SampleRate; bitRateNominal = (ushort)(info.VorbisParameters.BitRateNominal / 1000); // Integer division } else if (contents.Equals(CONTENTS_OPUS)) { channelsArrangement = getArrangementFromCode(info.OpusParameters.OutputChannelCount); sampleRate = (int)info.OpusParameters.InputSampleRate; // No nominal bitrate for OPUS } samples = info.Samples; result = true; } return(result); }
public static void FromStream(Stream source, MetaDataIO meta, ReadTagParams readTagParams, uint chunkSize) { long position = source.Position; long initialPos = position; string key, value; int size; byte[] data = new byte[chunkSize]; long maxPos = initialPos + chunkSize - 4; // 4 being the "INFO" purpose that belongs to the chunk while (source.Position < maxPos) { // Key source.Read(data, 0, 4); key = Utils.Latin1Encoding.GetString(data, 0, 4); // Size source.Read(data, 0, 4); size = StreamUtils.DecodeInt32(data); // Do _NOT_ use StreamUtils.ReadNullTerminatedString because non-textual fields may be found here (e.g. NITR) if (size > 0) { source.Read(data, 0, size); // Manage parasite zeroes at the end of data if (source.Position < maxPos && source.ReadByte() != 0) { source.Seek(-1, SeekOrigin.Current); } value = Utils.Latin1Encoding.GetString(data, 0, size); meta.SetMetaField("info." + key, Utils.StripEndingZeroChars(value), readTagParams.ReadAllMetaFrames); } } }
public static void FromStream(Stream source, MetaDataIO meta, ReadTagParams readTagParams, uint chunkSize) { IList <string> position = new List <string>(); bool inList = false; int listDepth = 0; int listCounter = 1; position.Add("ixml"); using (MemoryStream mem = new MemoryStream((int)chunkSize)) { StreamUtils.CopyStream(source, mem, (int)chunkSize); // Isolate XML structure in a clean memory chunk mem.Seek(0, SeekOrigin.Begin); using (XmlReader reader = XmlReader.Create(mem)) { while (reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: // Element start string key = reader.Name; if (inList && reader.Depth == listDepth + 1 && !key.EndsWith("COUNT", StringComparison.OrdinalIgnoreCase)) { key = key + "[" + listCounter + "]"; listCounter++; } if (!key.Equals("BWFXML", StringComparison.OrdinalIgnoreCase)) { position.Add(key); } if (!inList && reader.Name.EndsWith("LIST", StringComparison.OrdinalIgnoreCase)) { inList = true; listDepth = reader.Depth; listCounter = 1; } break; case XmlNodeType.Text: if (reader.Value != null && reader.Value.Length > 0) { meta.SetMetaField(getPosition(position), reader.Value, readTagParams.ReadAllMetaFrames); } break; case XmlNodeType.EndElement: // Element end position.RemoveAt(position.Count - 1); if (inList && reader.Name.EndsWith("LIST", StringComparison.OrdinalIgnoreCase)) { inList = false; } break; } } } } }
public bool Read(BinaryReader source, ReadTagParams readTagParams) { if (readTagParams.PrepareForWriting) { structureHelper.Clear(); } return(read(source, readTagParams)); }
protected override bool read(BinaryReader source, ReadTagParams readTagParams) { resetData(); bool result = readWAV(source.BaseStream, readTagParams); // Process data if loaded and header valid if (result) { bitrate = getBitrate(); duration = getDuration(); } return(result); }
private Boolean readTag(BinaryReader source, HeaderInfo Header, ReadTagParams readTagParams) { var chunk = new ChunkHeader(); String data; var result = false; var first = true; Int64 tagStart = -1; source.BaseStream.Seek(40, SeekOrigin.Begin); do { // Read chunk header (length : 8 bytes) chunk.ID = Utils.Latin1Encoding.GetString(source.ReadBytes(4)); chunk.Size = StreamUtils.ReverseUInt32(source.ReadUInt32()); // Read chunk data and set tag item if chunk header valid if (headerEndReached(chunk)) { break; } if (first) { tagStart = source.BaseStream.Position - 8; first = false; } tagExists = true; // If something else than mandatory info is stored, we can consider metadata is present data = Encoding.UTF8.GetString(source.ReadBytes((Int32)chunk.Size)).Trim(); SetMetaField(chunk.ID, data, readTagParams.ReadAllMetaFrames); result = true; } while (source.BaseStream.Position < source.BaseStream.Length); if (readTagParams.PrepareForWriting) { // Zone goes from the first field after COMM to the last field before DSIZ if (-1 == tagStart) { structureHelper.AddZone(source.BaseStream.Position - 8, 0); } else { structureHelper.AddZone(tagStart, (Int32)(source.BaseStream.Position - tagStart - 8)); } structureHelper.AddSize(12, (UInt32)Header.Size); } return(result); }
protected override bool read(BinaryReader source, ReadTagParams readTagParams) { bool result = true; SPCHeader header = new SPCHeader(); SPCExTags footer = new SPCExTags(); header.Reset(); footer.Reset(); resetData(); source.BaseStream.Seek(sizeInfo.ID3v2Size, SeekOrigin.Begin); if (!readHeader(source, ref header)) { throw new InvalidDataException("Not a SPC file"); } // Reads the header tag if (SPCHeader.TAG_IN_HEADER == header.TagInHeader) { tagExists = true; source.BaseStream.Seek(REGISTERS_LENGTH, SeekOrigin.Current); readHeaderTags(source, ref header, readTagParams); } AudioDataOffset = source.BaseStream.Position; // Reads extended tag if (source.BaseStream.Length > SPC_RAW_LENGTH) { source.BaseStream.Seek(SPC_RAW_LENGTH, SeekOrigin.Begin); readExtendedData(source, ref footer, readTagParams); } else { if (readTagParams.PrepareForWriting) { structureHelper.AddZone(SPC_RAW_LENGTH, 0, ZONE_EXTENDED); } } AudioDataSize = sizeInfo.FileSize - header.Size - footer.Size; bitrate = AudioDataSize * 8 / duration; return(result); }
// === PRIVATE METHODS === private bool readHeader(BufferedBinaryReader source, ReadTagParams readTagParams) { string str; if (GYM_SIGNATURE.Equals(Utils.Latin1Encoding.GetString(source.ReadBytes(GYM_SIGNATURE.Length)))) { if (readTagParams.PrepareForWriting) { structureHelper.AddZone(source.Position, 416, CORE_SIGNATURE); } tagExists = true; str = Utils.StripEndingZeroChars(Encoding.UTF8.GetString(source.ReadBytes(32))).Trim(); tagData.IntegrateValue(TagData.TAG_FIELD_TITLE, str); str = Utils.StripEndingZeroChars(Encoding.UTF8.GetString(source.ReadBytes(32))).Trim(); tagData.IntegrateValue(TagData.TAG_FIELD_ALBUM, str); str = Utils.StripEndingZeroChars(Encoding.UTF8.GetString(source.ReadBytes(32))).Trim(); tagData.IntegrateValue(TagData.TAG_FIELD_COPYRIGHT, str); str = Utils.StripEndingZeroChars(Encoding.UTF8.GetString(source.ReadBytes(32))).Trim(); tagData.AdditionalFields.Add(new MetaFieldInfo(getImplementedTagType(), "EMULATOR", str)); str = Utils.StripEndingZeroChars(Encoding.UTF8.GetString(source.ReadBytes(32))).Trim(); tagData.AdditionalFields.Add(new MetaFieldInfo(getImplementedTagType(), "DUMPER", str)); str = Utils.StripEndingZeroChars(Encoding.UTF8.GetString(source.ReadBytes(256))).Trim(); tagData.IntegrateValue(TagData.TAG_FIELD_COMMENT, str); loopStart = source.ReadUInt32(); uint packedSize = source.ReadUInt32(); AudioDataOffset = source.Position; AudioDataSize = sizeInfo.FileSize - AudioDataOffset; if (packedSize > 0) { LogDelegator.GetLogDelegate()(Log.LV_WARNING, "GZIP-compressed files are not supported"); // will be as soon as I find a sample to test with return(false); } return(true); } else { LogDelegator.GetLogDelegate()(Log.LV_ERROR, "Not a GYM file"); return(false); } }
private void readTags(BinaryReader source, long offset, long size, ReadTagParams readTagParams) { source.BaseStream.Seek(offset, SeekOrigin.Begin); int nbTags = StreamUtils.DecodeBEInt32(source.ReadBytes(4)); for (int i = 0; i < nbTags; i++) { source.BaseStream.Seek(1, SeekOrigin.Current); // No idea what this byte is int keyLength = StreamUtils.DecodeBEInt32(source.ReadBytes(4)); int valueLength = StreamUtils.DecodeBEInt32(source.ReadBytes(4)); string key = Encoding.UTF8.GetString(source.ReadBytes(keyLength)); string value = Encoding.UTF8.GetString(source.ReadBytes(valueLength)).Trim(); SetMetaField(key, value, readTagParams.ReadAllMetaFrames); if ("codec".Equals(key)) { codec = value; } } }
public void ReadPicture(Stream s, ReadTagParams readTagParams) { int picturePosition; long initPosition = s.Position; VorbisMetaDataBlockPicture block = ReadMetadataBlockPicture(s); if (block.picType.Equals(PictureInfo.PIC_TYPE.Unsupported)) { addPictureToken(getImplementedTagType(), (byte)block.nativePicCode); picturePosition = takePicturePosition(getImplementedTagType(), (byte)block.nativePicCode); } else { addPictureToken(block.picType); picturePosition = takePicturePosition(block.picType); } if (readTagParams.ReadPictures || readTagParams.PictureStreamHandler != null) { PictureInfo picInfo = new PictureInfo(ImageUtils.GetImageFormatFromMimeType(block.mimeType), block.picType, getImplementedTagType(), block.nativePicCode, picturePosition); picInfo.Description = block.description; picInfo.PictureData = new byte[block.picDataLength]; s.Seek(initPosition + block.picDataOffset, SeekOrigin.Begin); s.Read(picInfo.PictureData, 0, block.picDataLength); tagData.Pictures.Add(picInfo); if (!tagExists) { tagExists = true; } if (readTagParams.PictureStreamHandler != null) { MemoryStream mem = new MemoryStream(picInfo.PictureData); readTagParams.PictureStreamHandler(ref mem, picInfo.PicType, picInfo.NativeFormat, picInfo.TagType, picInfo.NativePicCode, picInfo.Position); mem.Close(); } } }
protected override Boolean read(BinaryReader source, ReadTagParams readTagParams) { var reader = new BufferedBinaryReader(source.BaseStream); // Reset and load tag data from file to variable ResetData(); tagVersion = TAG_VERSION_1_0; var result = ReadTag(reader); // Process data if loaded successfuly if (result) { tagExists = true; } else { ResetData(); } return(result); }
protected override bool read(BinaryReader source, ReadTagParams readTagParams) { bool result = true; PSFHeader header = new PSFHeader(); PSFTag tag = new PSFTag(); header.Reset(); tag.Reset(); resetData(); isValid = readHeader(source, ref header); if (!isValid) { throw new InvalidDataException("Not a PSF file"); } AudioDataOffset = 0; if (source.BaseStream.Length > HEADER_LENGTH + header.CompressedProgramLength + header.ReservedAreaLength) { source.BaseStream.Seek((long)(4 + header.CompressedProgramLength + header.ReservedAreaLength), SeekOrigin.Current); if (!readTag(source, ref tag, readTagParams)) { throw new InvalidDataException("Not a PSF tag"); } tagExists = true; } AudioDataSize = sizeInfo.FileSize - tag.size; version = header.VersionByte; bitrate = AudioDataSize * 8 / duration; return(result); }
// Specific implementation for OGG container (multiple pages with limited size) // TODO DOC // Simplified implementation of MetaDataIO tweaked for OGG-Vorbis specifics, i.e. // - tag spans over multiple pages, each having its own header // - last page may include whole or part of 3rd Vorbis header (setup header) public bool Write(BinaryReader r, BinaryWriter w, TagData tag) { bool result = true; int writtenPages = 0; long nextPageOffset = 0; // Read all the fields in the existing tag (including unsupported fields) ReadTagParams readTagParams = new ReadTagParams(true, true); readTagParams.PrepareForWriting = true; Read(r, readTagParams); // Get "unpaged" virtual stream to be written, containing the vorbis tag (=comment header) using (MemoryStream stream = new MemoryStream((int)(info.SetupHeaderEnd - info.CommentHeaderStart))) { stream.Write(Utils.Latin1Encoding.GetBytes(VORBIS_TAG_ID), 0, VORBIS_TAG_ID.Length); vorbisTag.Write(stream, tag); long newTagSize = stream.Position; int setupHeaderSize = (int)(info.SetupHeaderEnd - info.SetupHeaderStart); // Append the setup header in the "unpaged" virtual stream r.BaseStream.Seek(info.SetupHeaderStart, SeekOrigin.Begin); if (1 == info.SetupHeaderSpanPages) { StreamUtils.CopyStream(r.BaseStream, stream, setupHeaderSize); } else { // TODO - handle case where initial setup header spans across two pages LogDelegator.GetLogDelegate()(Log.LV_ERROR, "ATL does not yet handle the case where Vorbis setup header spans across two OGG pages"); return(false); } // Construct the entire segments table int commentsHeader_nbSegments = (int)Math.Ceiling(1.0 * newTagSize / 255); byte commentsHeader_remainingBytesInLastSegment = (byte)(newTagSize % 255); int setupHeader_nbSegments = (int)Math.Ceiling(1.0 * setupHeaderSize / 255); byte setupHeader_remainingBytesInLastSegment = (byte)(setupHeaderSize % 255); byte[] entireSegmentsTable = new byte[commentsHeader_nbSegments + setupHeader_nbSegments]; for (int i = 0; i < commentsHeader_nbSegments - 1; i++) { entireSegmentsTable[i] = 255; } entireSegmentsTable[commentsHeader_nbSegments - 1] = commentsHeader_remainingBytesInLastSegment; for (int i = commentsHeader_nbSegments; i < commentsHeader_nbSegments + setupHeader_nbSegments - 1; i++) { entireSegmentsTable[i] = 255; } entireSegmentsTable[commentsHeader_nbSegments + setupHeader_nbSegments - 1] = setupHeader_remainingBytesInLastSegment; int nbPageHeaders = (int)Math.Ceiling((commentsHeader_nbSegments + setupHeader_nbSegments) / 255.0); int totalPageHeadersSize = (nbPageHeaders * 27) + setupHeader_nbSegments + commentsHeader_nbSegments; // Resize the whole virtual stream once and for all to avoid multiple reallocations while repaging stream.SetLength(stream.Position + totalPageHeadersSize); /// Repage comments header & setup header within the virtual stream stream.Seek(0, SeekOrigin.Begin); OggHeader header = new OggHeader() { ID = OGG_PAGE_ID, StreamVersion = info.CommentHeader.StreamVersion, TypeFlag = 0, AbsolutePosition = ulong.MaxValue, Serial = info.CommentHeader.Serial, PageNumber = 1, Checksum = 0 }; int segmentsLeftToPage = commentsHeader_nbSegments + setupHeader_nbSegments; int bytesLeftToPage = (int)newTagSize + setupHeaderSize; int pagedSegments = 0; int pagedBytes = 0; long position; BinaryWriter virtualW = new BinaryWriter(stream); IList <KeyValuePair <long, int> > pageHeaderOffsets = new List <KeyValuePair <long, int> >(); // Repaging while (segmentsLeftToPage > 0) { header.Segments = (byte)Math.Min(255, segmentsLeftToPage); header.LacingValues = new byte[header.Segments]; if (segmentsLeftToPage == header.Segments) { header.AbsolutePosition = 0; // Last header page has its absolutePosition = 0 } Array.Copy(entireSegmentsTable, pagedSegments, header.LacingValues, 0, header.Segments); position = stream.Position; // Push current data to write header StreamUtils.CopySameStream(stream, stream.Position, stream.Position + header.GetHeaderSize(), bytesLeftToPage); stream.Seek(position, SeekOrigin.Begin); pageHeaderOffsets.Add(new KeyValuePair <long, int>(position, header.GetPageLength() + header.GetHeaderSize())); header.WriteToStream(virtualW); stream.Seek(header.GetPageLength(), SeekOrigin.Current); pagedSegments += header.Segments; segmentsLeftToPage -= header.Segments; pagedBytes += header.GetPageLength(); bytesLeftToPage -= header.GetPageLength(); header.PageNumber++; if (0 == header.TypeFlag) { header.TypeFlag = 1; } } writtenPages = header.PageNumber - 1; // Generate CRC32 of created pages uint crc; byte[] data; foreach (KeyValuePair <long, int> kv in pageHeaderOffsets) { crc = 0; stream.Seek(kv.Key, SeekOrigin.Begin); data = new byte[kv.Value]; stream.Read(data, 0, kv.Value); crc = OggCRC32.CalculateCRC(crc, data, (uint)kv.Value); stream.Seek(kv.Key + 22, SeekOrigin.Begin); // Position of CRC within OGG header virtualW.Write(crc); } /// Insert the virtual paged stream into the actual file long oldHeadersSize = info.SetupHeaderEnd - info.CommentHeaderStart; long newHeadersSize = stream.Length; if (newHeadersSize > oldHeadersSize) // Need to build a larger file { StreamUtils.LengthenStream(w.BaseStream, info.CommentHeaderEnd, (uint)(newHeadersSize - oldHeadersSize)); } else if (newHeadersSize < oldHeadersSize) // Need to reduce file size { StreamUtils.ShortenStream(w.BaseStream, info.CommentHeaderEnd, (uint)(oldHeadersSize - newHeadersSize)); } // Rewrite Comment and Setup headers w.BaseStream.Seek(info.CommentHeaderStart, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin); StreamUtils.CopyStream(stream, w.BaseStream); nextPageOffset = info.CommentHeaderStart + stream.Length; } // If the number of written pages is different than the number of previous existing pages, // all the next pages of the file need to be renumbered, and their CRC accordingly recalculated if (writtenPages != info.CommentHeaderSpanPages + info.SetupHeaderSpanPages - 1) { OggHeader header = new OggHeader(); byte[] data; uint crc; do { w.BaseStream.Seek(nextPageOffset, SeekOrigin.Begin); header.ReadFromStream(r); if (header.IsValid()) { // Rewrite page number writtenPages++; w.BaseStream.Seek(nextPageOffset + 18, SeekOrigin.Begin); w.Write(writtenPages); // Rewrite CRC w.BaseStream.Seek(nextPageOffset, SeekOrigin.Begin); data = new byte[header.GetHeaderSize() + header.GetPageLength()]; r.Read(data, 0, data.Length); // Checksum has to include its own location, as if it were 0 data[22] = 0; data[23] = 0; data[24] = 0; data[25] = 0; crc = OggCRC32.CalculateCRC(0, data, (uint)data.Length); r.BaseStream.Seek(nextPageOffset + 22, SeekOrigin.Begin); // Position of CRC within OGG header w.Write(crc); // To the next header nextPageOffset += data.Length; } else { LogDelegator.GetLogDelegate()(Log.LV_ERROR, "Invalid OGG header found; aborting writing operation"); // Throw exception ? return(false); } } while (0 == (header.TypeFlag & 0x04)); // 0x04 marks the last page of the logical bitstream } return(result); }
// === PUBLIC METHODS === public bool Read(BinaryReader source, SizeInfo sizeInfo, ReadTagParams readTagParams) { this.sizeInfo = sizeInfo; return(read(source, readTagParams)); }
private void readExtendedData(BinaryReader source, ref SPCExTags footer, ReadTagParams readTagParams) { long initialPosition = source.BaseStream.Position; footer.FormatTag = Utils.Latin1Encoding.GetString(source.ReadBytes(4)); if (XTENDED_TAG == footer.FormatTag) { tagExists = true; footer.Size = source.ReadUInt32(); byte ID, type; ushort size; string strData = ""; int intData = 0; long ticks = 0; long dataPosition = source.BaseStream.Position; while (source.BaseStream.Position < dataPosition + footer.Size - 4) { ID = source.ReadByte(); type = source.ReadByte(); size = source.ReadUInt16(); switch (type) { case XID6_TVAL: // Value is stored into the Size field if (ID == XID6_TRACK) // Specific case : upper byte is the number 0-99, lower byte is an optional ASCII character { intData = size >> 8; strData = intData.ToString(); byte optionalChar = (byte)(size & 0x00FF); if (optionalChar > 0x20) // Character is displayable { strData += (char)optionalChar; } } else { intData = size; strData = intData.ToString(); } break; case XID6_TSTR: intData = 0; strData = Utils.Latin1Encoding.GetString(source.ReadBytes(size)).Replace("\0", "").Trim(); while (source.BaseStream.Position < source.BaseStream.Length && 0 == source.ReadByte()) { ; // Skip parasite ending zeroes } if (source.BaseStream.Position < source.BaseStream.Length) { source.BaseStream.Seek(-1, SeekOrigin.Current); } break; case XID6_TINT: intData = source.ReadInt32(); strData = intData.ToString(); break; } if (XID6_LOOP == ID) { ticks += Math.Min(XID6_MAXTICKS, intData); } else if (XID6_LOOPX == ID) { ticks = ticks * Math.Min(XID6_MAXLOOP, (int)size); } else if (XID6_INTRO == ID) { ticks += Math.Min(XID6_MAXTICKS, intData); } else if (XID6_END == ID) { ticks += Math.Min(XID6_MAXTICKS, intData); } else if (XID6_FADE == ID) { ticks += Math.Min(XID6_MAXTICKS, intData); } SetMetaField(ID.ToString(), strData, readTagParams.ReadAllMetaFrames, ZONE_EXTENDED); } if (ticks > 0) { duration = Math.Round((double)ticks / XID6_TICKSSEC); } if (readTagParams.PrepareForWriting) { structureHelper.AddZone(initialPosition, (int)(source.BaseStream.Position - initialPosition), ZONE_EXTENDED); } } }
private void readHeaderTags(BinaryReader source, ref SPCHeader header, ReadTagParams readTagParams) { long initialPosition = source.BaseStream.Position; SetMetaField(HEADER_TITLE.ToString(), Utils.Latin1Encoding.GetString(source.ReadBytes(32)).Replace("\0", "").Trim(), readTagParams.ReadAllMetaFrames, ZONE_HEADER); SetMetaField(HEADER_ALBUM.ToString(), Utils.Latin1Encoding.GetString(source.ReadBytes(32)).Replace("\0", "").Trim(), readTagParams.ReadAllMetaFrames, ZONE_HEADER); SetMetaField(HEADER_DUMPERNAME.ToString(), Utils.Latin1Encoding.GetString(source.ReadBytes(16)).Replace("\0", "").Trim(), readTagParams.ReadAllMetaFrames, ZONE_HEADER); SetMetaField(HEADER_COMMENT.ToString(), Utils.Latin1Encoding.GetString(source.ReadBytes(32)).Replace("\0", "").Trim(), readTagParams.ReadAllMetaFrames, ZONE_HEADER); byte[] date, song, fade; // NB : Dump date is used to determine if the tag is binary or text-based. // It won't be recorded as a property of TSPC date = source.ReadBytes(11); song = source.ReadBytes(3); fade = source.ReadBytes(5); bool bin; int dateRes = isText(date); int songRes = isText(song); int fadeRes = isText(fade); //if ( 0 == (dateRes | songRes | fadeRes) ) // No time nor date -> use default //{ bin = true; //} //else if ((songRes != -1) && (fadeRes != -1)) // No time, or time is text { if (dateRes > 0) //If date is text, then tag is text { bin = false; } else if (0 == dateRes) //No date { bin = PREFER_BIN; //Times could still be binary (ex. 56 bin = '8' txt) } else if (-1 == dateRes) //Date contains invalid characters { bin = true; for (int i = 4; i < 8; i++) { bin = bin && (0 == date[i]); } } } else { bin = true; } int fadeVal; int songVal; if (bin) { fadeVal = fade[0] * 0x000001 + fade[1] * 0x0000FF + fade[2] * 0x00FF00 + fade[3] * 0xFF0000; if (fadeVal > 59999) { fadeVal = 59999; } songVal = song[0] * 0x01 + song[1] * 0x10; if (songVal > 959) { songVal = 959; } source.BaseStream.Seek(-1, SeekOrigin.Current); // We're one byte ahead SetMetaField(HEADER_FADE.ToString(), Utils.Latin1Encoding.GetString(fade), readTagParams.ReadAllMetaFrames, ZONE_HEADER); } else { fadeVal = TrackUtils.ExtractTrackNumber(Utils.Latin1Encoding.GetString(fade)); songVal = TrackUtils.ExtractTrackNumber(Utils.Latin1Encoding.GetString(song)); SetMetaField(HEADER_FADE.ToString(), Utils.Latin1Encoding.GetString(fade), readTagParams.ReadAllMetaFrames, ZONE_HEADER); } SetMetaField(HEADER_DUMPDATE.ToString(), Utils.Latin1Encoding.GetString(date), readTagParams.ReadAllMetaFrames, ZONE_HEADER); SetMetaField(HEADER_SONGLENGTH.ToString(), Utils.Latin1Encoding.GetString(song), readTagParams.ReadAllMetaFrames, ZONE_HEADER); // if fadeval > 0 alone, the fade is applied on the default 3:00 duration without extending it if (songVal > 0) { duration = Math.Round((double)fadeVal) + songVal; } SetMetaField(HEADER_ARTIST.ToString(), Utils.Latin1Encoding.GetString(source.ReadBytes(32)).Replace("\0", "").Trim(), readTagParams.ReadAllMetaFrames, ZONE_HEADER); header.Size += source.BaseStream.Position - initialPosition; if (readTagParams.PrepareForWriting) { structureHelper.AddZone(initialPosition, (int)(source.BaseStream.Position - initialPosition), ZONE_HEADER); } }
/* Unused for now * * // Get compression ratio * private double getCompressionRatio() * { * if (isValid()) * { * return (double)sizeInfo.FileSize / (samples * channels * bitsPerSample / 8.0) * 100; * } * else * { * return 0; * } * } */ public bool Read(BinaryReader source, AudioDataManager.SizeInfo sizeInfo, ReadTagParams readTagParams) { this.sizeInfo = sizeInfo; return(Read(source, readTagParams)); }
// TODO : support for CUESHEET block public bool Read(BinaryReader source, ReadTagParams readTagParams) { bool result = false; if (readTagParams.ReadTag && null == vorbisTag) { vorbisTag = new VorbisTag(false, false, false, false); } initialPaddingOffset = -1; initialPaddingSize = 0; byte[] aMetaDataBlockHeader; long position; uint blockLength; byte blockType; int blockIndex; bool isLast; bool paddingFound = false; long blockEndOffset = -1; readHeader(source); // Process data if loaded and header valid if (header.IsValid()) { int channels = (header.Info[12] >> 1) & 0x7; switch (channels) { case 0b0000: channelsArrangement = MONO; break; case 0b0001: channelsArrangement = STEREO; break; case 0b0010: channelsArrangement = ISO_3_0_0; break; case 0b0011: channelsArrangement = QUAD; break; case 0b0100: channelsArrangement = ISO_3_2_0; break; case 0b0101: channelsArrangement = ISO_3_2_1; break; case 0b0110: channelsArrangement = LRCLFECrLssRss; break; case 0b0111: channelsArrangement = LRCLFELrRrLssRss; break; case 0b1000: channelsArrangement = JOINT_STEREO_LEFT_SIDE; break; case 0b1001: channelsArrangement = JOINT_STEREO_RIGHT_SIDE; break; case 0b1010: channelsArrangement = JOINT_STEREO_MID_SIDE; break; default: channelsArrangement = UNKNOWN; break; } sampleRate = header.Info[10] << 12 | header.Info[11] << 4 | header.Info[12] >> 4; bitsPerSample = (byte)(((header.Info[12] & 1) << 4) | (header.Info[13] >> 4) + 1); samples = header.Info[14] << 24 | header.Info[15] << 16 | header.Info[16] << 8 | header.Info[17]; if (0 == (header.MetaDataBlockHeader[1] & FLAG_LAST_METADATA_BLOCK)) // metadata block exists { blockIndex = 0; vorbisTag.Clear(); if (readTagParams.PrepareForWriting) { if (null == zones) { zones = new List <Zone>(); } else { zones.Clear(); } blockEndOffset = source.BaseStream.Position; } do // Read all metadata blocks { aMetaDataBlockHeader = source.ReadBytes(4); isLast = (aMetaDataBlockHeader[0] & FLAG_LAST_METADATA_BLOCK) > 0; // last flag ( first bit == 1 ) blockIndex++; blockLength = StreamUtils.DecodeBEUInt24(aMetaDataBlockHeader, 1); blockType = (byte)(aMetaDataBlockHeader[0] & 0x7F); // decode metablock type position = source.BaseStream.Position; if (blockType == META_VORBIS_COMMENT) // Vorbis metadata { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(blockType + "", position - 4, (int)blockLength + 4, new byte[0], blockType)); } vorbisTag.Read(source, readTagParams); } else if ((blockType == META_PADDING) && (!paddingFound)) // Padding block (skip any other padding block) { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(PADDING_ZONE_NAME, position - 4, (int)blockLength + 4, new byte[0], blockType)); } initialPaddingSize = blockLength; initialPaddingOffset = position; paddingFound = true; source.BaseStream.Seek(blockLength, SeekOrigin.Current); } else if (blockType == META_PICTURE) // Picture (NB: as per FLAC specs, pictures must be embedded at the FLAC level, not in the VorbisComment !) { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(blockType + "", position - 4, (int)blockLength + 4, new byte[0], blockType)); } vorbisTag.ReadPicture(source.BaseStream, readTagParams); } else // Unhandled block; needs to be zoned anyway to be able to manage the 'isLast' flag at write-time { if (readTagParams.PrepareForWriting) { zones.Add(new Zone(blockType + "", position - 4, (int)blockLength + 4, new byte[0], blockType)); } } if (blockType < 7) { source.BaseStream.Seek(position + blockLength, SeekOrigin.Begin); blockEndOffset = position + blockLength; } else { // Abnormal header : incorrect size and/or misplaced last-metadata-block flag break; } }while (!isLast); if (readTagParams.PrepareForWriting) { bool vorbisTagFound = false; bool pictureFound = false; foreach (Zone zone in zones) { if (zone.Flag == META_PICTURE) { pictureFound = true; } else if (zone.Flag == META_VORBIS_COMMENT) { vorbisTagFound = true; } } if (!vorbisTagFound) { zones.Add(new Zone(META_VORBIS_COMMENT + "", blockEndOffset, 0, new byte[0], META_VORBIS_COMMENT)); } if (!pictureFound) { zones.Add(new Zone(META_PICTURE + "", blockEndOffset, 0, new byte[0], META_PICTURE)); } // Padding must be the last block for it to correctly absorb size variations of the other blocks if (!paddingFound && Settings.AddNewPadding) { zones.Add(new Zone(PADDING_ZONE_NAME, blockEndOffset, 0, new byte[0], META_PADDING)); } } } } if (isValid()) { audioOffset = source.BaseStream.Position; // we need that to calculate the bitrate result = true; } return(result); }
// ---------- SUPPORT METHODS private bool readWAV(Stream source, ReadTagParams readTagParams) { bool result = true; uint riffChunkSize; long riffChunkSizePos; byte[] data = new byte[4]; source.Seek(0, SeekOrigin.Begin); // Read header source.Read(data, 0, 4); string str = Utils.Latin1Encoding.GetString(data); if (str.Equals(HEADER_RIFF)) { _isLittleEndian = true; } else if (str.Equals(HEADER_RIFX)) { _isLittleEndian = false; } else { return(false); } // Force creation of FileStructureHelper with detected endianness structureHelper = new FileStructureHelper(isLittleEndian); id3v2StructureHelper = new FileStructureHelper(isLittleEndian); riffChunkSizePos = source.Position; source.Read(data, 0, 4); if (isLittleEndian) { riffChunkSize = StreamUtils.DecodeUInt32(data); } else { riffChunkSize = StreamUtils.DecodeBEUInt32(data); } // Format code source.Read(data, 0, 4); str = Utils.Latin1Encoding.GetString(data); if (!str.Equals(FORMAT_WAVE)) { return(false); } string subChunkId; uint chunkSize; long chunkDataPos; bool foundSample = false; bool foundBext = false; bool foundInfo = false; bool foundIXml = false; // Sub-chunks loop while (source.Position < riffChunkSize + 8) { // Chunk ID source.Read(data, 0, 4); if (0 == data[0]) // Sometimes data segment ends with a parasite null byte { source.Seek(-3, SeekOrigin.Current); source.Read(data, 0, 4); } subChunkId = Utils.Latin1Encoding.GetString(data); // Chunk size source.Read(data, 0, 4); if (isLittleEndian) { chunkSize = StreamUtils.DecodeUInt32(data); } else { chunkSize = StreamUtils.DecodeBEUInt32(data); } chunkDataPos = source.Position; if (subChunkId.Equals(CHUNK_FORMAT)) { source.Read(data, 0, 2); if (isLittleEndian) { formatId = StreamUtils.DecodeUInt16(data); } else { formatId = StreamUtils.DecodeBEUInt16(data); } source.Read(data, 0, 2); if (isLittleEndian) { channelsArrangement = ChannelsArrangements.GuessFromChannelNumber(StreamUtils.DecodeUInt16(data)); } else { channelsArrangement = ChannelsArrangements.GuessFromChannelNumber(StreamUtils.DecodeBEUInt16(data)); } source.Read(data, 0, 4); if (isLittleEndian) { sampleRate = StreamUtils.DecodeUInt32(data); } else { sampleRate = StreamUtils.DecodeBEUInt32(data); } source.Read(data, 0, 4); if (isLittleEndian) { bytesPerSecond = StreamUtils.DecodeUInt32(data); } else { bytesPerSecond = StreamUtils.DecodeBEUInt32(data); } source.Seek(2, SeekOrigin.Current); // BlockAlign source.Read(data, 0, 2); if (isLittleEndian) { bitsPerSample = StreamUtils.DecodeUInt16(data); } else { bitsPerSample = StreamUtils.DecodeBEUInt16(data); } } else if (subChunkId.Equals(CHUNK_DATA)) { headerSize = riffChunkSize - chunkSize; } else if (subChunkId.Equals(CHUNK_FACT)) { source.Read(data, 0, 4); if (isLittleEndian) { sampleNumber = StreamUtils.DecodeInt32(data); } else { sampleNumber = StreamUtils.DecodeBEInt32(data); } } else if (subChunkId.Equals(CHUNK_SAMPLE)) { structureHelper.AddZone(source.Position - 8, (int)(chunkSize + 8), subChunkId); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, subChunkId); foundSample = true; tagExists = true; SampleTag.FromStream(source, this, readTagParams); } else if (subChunkId.Equals(CHUNK_BEXT)) { structureHelper.AddZone(source.Position - 8, (int)(chunkSize + 8), subChunkId); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, subChunkId); foundBext = true; tagExists = true; BextTag.FromStream(source, this, readTagParams); } else if (subChunkId.Equals(CHUNK_INFO)) { // Purpose of the list should be INFO source.Read(data, 0, 4); string purpose = Utils.Latin1Encoding.GetString(data, 0, 4); if (purpose.Equals(InfoTag.PURPOSE_INFO)) { structureHelper.AddZone(source.Position - 12, (int)(chunkSize + 8), subChunkId); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, subChunkId); foundInfo = true; tagExists = true; InfoTag.FromStream(source, this, readTagParams, chunkSize); } } else if (subChunkId.Equals(CHUNK_IXML)) { structureHelper.AddZone(source.Position - 8, (int)(chunkSize + 8), subChunkId); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, subChunkId); foundIXml = true; tagExists = true; IXmlTag.FromStream(source, this, readTagParams, chunkSize); } else if (subChunkId.Equals(CHUNK_ID3)) { id3v2Offset = source.Position; // Zone is already added by Id3v2.Read id3v2StructureHelper.AddZone(id3v2Offset - 8, (int)(chunkSize + 8), subChunkId); id3v2StructureHelper.AddSize(riffChunkSizePos, riffChunkSize, subChunkId); } source.Seek(chunkDataPos + chunkSize, SeekOrigin.Begin); } // Add zone placeholders for future tag writing if (readTagParams.PrepareForWriting) { if (!foundSample) { structureHelper.AddZone(source.Position, 0, CHUNK_SAMPLE); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, CHUNK_SAMPLE); } if (!foundBext) { structureHelper.AddZone(source.Position, 0, CHUNK_BEXT); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, CHUNK_BEXT); } if (!foundInfo) { structureHelper.AddZone(source.Position, 0, CHUNK_INFO); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, CHUNK_INFO); } if (!foundIXml) { structureHelper.AddZone(source.Position, 0, CHUNK_IXML); structureHelper.AddSize(riffChunkSizePos, riffChunkSize, CHUNK_IXML); } } // ID3 zone should be set as the very last one for Windows to be able to read the LIST INFO zone properly if (-1 == id3v2Offset) { id3v2Offset = 0; // Switch status to "tried to read, but nothing found" if (readTagParams.PrepareForWriting) { id3v2StructureHelper.AddZone(source.Position, 0, CHUNK_ID3); id3v2StructureHelper.AddSize(riffChunkSizePos, riffChunkSize, CHUNK_ID3); } } return(result); }
// === PRIVATE METHODS === private bool readHeader(BinaryReader source, ReadTagParams readTagParams) { int nbSamples, loopNbSamples; int nbLoops = LOOP_COUNT_DEFAULT; int recordingRate = RECORDING_RATE_DEFAULT; byte[] headerSignature = source.ReadBytes(VGM_SIGNATURE.Length); if (VGM_SIGNATURE.Equals(Utils.Latin1Encoding.GetString(headerSignature))) { source.BaseStream.Seek(4, SeekOrigin.Current); // EOF offset version = source.ReadInt32(); source.BaseStream.Seek(8, SeekOrigin.Current); // Clocks gd3TagOffset = source.ReadInt32() + (int)source.BaseStream.Position - 4; if (/*gd3TagOffset > 0 && */ readTagParams.PrepareForWriting) { if (gd3TagOffset > VGM_HEADER_SIZE) { structureHelper.AddZone(gd3TagOffset, (int)sizeInfo.FileSize - gd3TagOffset); structureHelper.AddIndex(source.BaseStream.Position - 4, gd3TagOffset, true); } else { structureHelper.AddZone(sizeInfo.FileSize, 0); structureHelper.AddIndex(source.BaseStream.Position - 4, (int)sizeInfo.FileSize, true); } } nbSamples = source.ReadInt32(); source.BaseStream.Seek(4, SeekOrigin.Current); // Loop offset loopNbSamples = source.ReadInt32(); if (version >= 0x00000101) { recordingRate = source.ReadInt32(); } if (version >= 0x00000160) { source.BaseStream.Seek(0x7E, SeekOrigin.Begin); nbLoops -= source.ReadSByte(); // Loop base } if (version >= 0x00000151) { source.BaseStream.Seek(0x7F, SeekOrigin.Begin); nbLoops = nbLoops * source.ReadByte(); // Loop modifier } duration = (nbSamples * 1000.0 / sampleRate) + (nbLoops * (loopNbSamples * 1000.0 / sampleRate)); if (Settings.GYM_VGM_playbackRate > 0) { duration = duration * (Settings.GYM_VGM_playbackRate / (double)recordingRate); } if (nbLoops > 0) { duration += FADEOUT_DURATION_DEFAULT; } bitrate = (sizeInfo.FileSize - VGM_HEADER_SIZE) * 8 / duration; // TODO - use unpacked size if applicable, and not raw file size return(true); } else { LogDelegator.GetLogDelegate()(Log.LV_ERROR, "Not a VGM file"); return(false); } }
// ------ ABSTRACT METHODS ----------------------------------------------------- abstract protected bool read(BinaryReader source, ReadTagParams readTagParams);
public void readTagField(BinaryReader source, string zoneCode, string fieldName, ushort fieldDataType, int fieldDataSize, ReadTagParams readTagParams, bool isExtendedHeader = false, ushort languageIndex = 0, ushort streamNumber = 0) { string fieldValue = ""; bool setMeta = true; addFrameClass(fieldName, fieldDataType); if (0 == fieldDataType) // Unicode string { fieldValue = Utils.StripEndingZeroChars(Encoding.Unicode.GetString(source.ReadBytes(fieldDataSize))); } else if (1 == fieldDataType) // Byte array { if (fieldName.ToUpper().Equals("WM/PICTURE")) { byte picCode = source.ReadByte(); // TODO factorize : abstract PictureTypeDecoder + unsupported / supported decision in MetaDataIO ? PictureInfo.PIC_TYPE picType = ID3v2.DecodeID3v2PictureType(picCode); int picturePosition; if (picType.Equals(PictureInfo.PIC_TYPE.Unsupported)) { addPictureToken(MetaDataIOFactory.TAG_NATIVE, picCode); picturePosition = takePicturePosition(MetaDataIOFactory.TAG_NATIVE, picCode); } else { addPictureToken(picType); picturePosition = takePicturePosition(picType); } if (readTagParams.ReadPictures) { int picSize = source.ReadInt32(); string mimeType = StreamUtils.ReadNullTerminatedString(source, Encoding.Unicode); string description = StreamUtils.ReadNullTerminatedString(source, Encoding.Unicode); PictureInfo picInfo = PictureInfo.fromBinaryData(source.BaseStream, picSize, picType, getImplementedTagType(), picCode, picturePosition); picInfo.Description = description; tagData.Pictures.Add(picInfo); } setMeta = false; } else { source.BaseStream.Seek(fieldDataSize, SeekOrigin.Current); } } else if (2 == fieldDataType) // 16-bit Boolean (metadata); 32-bit Boolean (extended header) { if (isExtendedHeader) { fieldValue = source.ReadUInt32().ToString(); } else { fieldValue = source.ReadUInt16().ToString(); } } else if (3 == fieldDataType) // 32-bit unsigned integer { uint intValue = source.ReadUInt32(); if (fieldName.Equals("WM/GENRE", StringComparison.OrdinalIgnoreCase)) { intValue++; } fieldValue = intValue.ToString(); } else if (4 == fieldDataType) // 64-bit unsigned integer { fieldValue = source.ReadUInt64().ToString(); } else if (5 == fieldDataType) // 16-bit unsigned integer { fieldValue = source.ReadUInt16().ToString(); } else if (6 == fieldDataType) // 128-bit GUID; unused for now { source.BaseStream.Seek(fieldDataSize, SeekOrigin.Current); } if (setMeta) { SetMetaField(fieldName.Trim(), fieldValue, readTagParams.ReadAllMetaFrames, zoneCode, 0, streamNumber, decodeLanguage(source.BaseStream, languageIndex)); } }
private bool getInfo(BufferedBinaryReader source, FileInfo info, ReadTagParams readTagParams) { // Get info from file bool result = false; bool isValidHeader = false; // Check for ID3v2 (NB : this case should not even exist since OGG has its own native tagging system, and is not deemed compatible with ID3v2 according to the ID3 FAQ) source.Seek(sizeInfo.ID3v2Size, SeekOrigin.Begin); // Read global file header info.IdentificationHeader.ReadFromStream(source); if (info.IdentificationHeader.IsValid()) { source.Seek(sizeInfo.ID3v2Size + info.IdentificationHeader.Segments + 27, SeekOrigin.Begin); // 27 being the size from 'ID' to 'Segments' // Read Vorbis or Opus stream info long position = source.Position; String headerStart = Utils.Latin1Encoding.GetString(source.ReadBytes(3)); source.Seek(position, SeekOrigin.Begin); if (VORBIS_HEADER_ID.StartsWith(headerStart)) { contents = CONTENTS_VORBIS; info.VorbisParameters.ID = Utils.Latin1Encoding.GetString(source.ReadBytes(7)); isValidHeader = VORBIS_HEADER_ID.Equals(info.VorbisParameters.ID); info.VorbisParameters.BitstreamVersion = source.ReadBytes(4); info.VorbisParameters.ChannelMode = source.ReadByte(); info.VorbisParameters.SampleRate = source.ReadInt32(); info.VorbisParameters.BitRateMaximal = source.ReadInt32(); info.VorbisParameters.BitRateNominal = source.ReadInt32(); info.VorbisParameters.BitRateMinimal = source.ReadInt32(); info.VorbisParameters.BlockSize = source.ReadByte(); info.VorbisParameters.StopFlag = source.ReadByte(); } else if (OPUS_HEADER_ID.StartsWith(headerStart)) { contents = CONTENTS_OPUS; info.OpusParameters.ID = Utils.Latin1Encoding.GetString(source.ReadBytes(8)); isValidHeader = OPUS_HEADER_ID.Equals(info.OpusParameters.ID); info.OpusParameters.Version = source.ReadByte(); info.OpusParameters.OutputChannelCount = source.ReadByte(); info.OpusParameters.PreSkip = source.ReadUInt16(); //info.OpusParameters.InputSampleRate = source.ReadUInt32(); info.OpusParameters.InputSampleRate = 48000; // Actual sample rate is hardware-dependent. Let's assume for now that the hardware ATL runs on supports 48KHz source.Seek(4, SeekOrigin.Current); info.OpusParameters.OutputGain = source.ReadInt16(); info.OpusParameters.ChannelMappingFamily = source.ReadByte(); if (info.OpusParameters.ChannelMappingFamily > 0) { info.OpusParameters.StreamCount = source.ReadByte(); info.OpusParameters.CoupledStreamCount = source.ReadByte(); info.OpusParameters.ChannelMapping = new byte[info.OpusParameters.OutputChannelCount]; for (int i = 0; i < info.OpusParameters.OutputChannelCount; i++) { info.OpusParameters.ChannelMapping[i] = source.ReadByte(); } } } if (isValidHeader) { info.CommentHeaderStart = source.Position; IList <long> pagePos = new List <long>(); // Reads all related Vorbis pages that describe Comment and Setup headers // and concatenate their content into a single, continuous data stream bool loop = true; bool first = true; using (MemoryStream s = new MemoryStream()) { // Reconstruct the whole Comment header from OGG pages to a MemoryStream while (loop) { info.SetupHeaderEnd = source.Position; // When the loop stops, cursor is starting to read a brand new page located after Comment _and_ Setup headers info.CommentHeader.ID = Utils.Latin1Encoding.GetString(source.ReadBytes(4)); info.CommentHeader.StreamVersion = source.ReadByte(); info.CommentHeader.TypeFlag = source.ReadByte(); // 0 marks a new page if (0 == info.CommentHeader.TypeFlag) { loop = first; } if (loop) { info.CommentHeader.AbsolutePosition = source.ReadUInt64(); info.CommentHeader.Serial = source.ReadInt32(); info.CommentHeader.PageNumber = source.ReadInt32(); info.CommentHeader.Checksum = source.ReadUInt32(); info.CommentHeader.Segments = source.ReadByte(); info.CommentHeader.LacingValues = source.ReadBytes(info.CommentHeader.Segments); s.Write(source.ReadBytes(info.CommentHeader.GetPageLength()), 0, info.CommentHeader.GetPageLength()); pagePos.Add(info.SetupHeaderEnd); } first = false; } if (readTagParams.PrepareForWriting) // Metrics to prepare writing { if (pagePos.Count > 1) { source.Position = pagePos[pagePos.Count - 2]; } else { source.Position = pagePos[0]; } // Determine the boundaries of 3rd header (Setup header) by searching from last-but-one page if (StreamUtils.FindSequence(source, Utils.Latin1Encoding.GetBytes(VORBIS_SETUP_ID))) { info.SetupHeaderStart = source.Position - VORBIS_SETUP_ID.Length; info.CommentHeaderEnd = info.SetupHeaderStart; if (pagePos.Count > 1) { int firstSetupPage = -1; for (int i = 1; i < pagePos.Count; i++) { if (info.CommentHeaderEnd < pagePos[i]) { info.CommentHeaderSpanPages = i - 1; firstSetupPage = i - 1; } if (info.SetupHeaderEnd < pagePos[i]) { info.SetupHeaderSpanPages = i - firstSetupPage; } } /// Not found yet => comment header takes up all pages, and setup header is on the end of the last page if (-1 == firstSetupPage) { info.CommentHeaderSpanPages = pagePos.Count; info.SetupHeaderSpanPages = 1; } } else { info.CommentHeaderSpanPages = 1; info.SetupHeaderSpanPages = 1; } } } // Get total number of samples info.Samples = getSamples(source); // Read metadata from the reconstructed Comment header inside the memoryStream if (readTagParams.ReadTag) { BinaryReader msr = new BinaryReader(s); s.Seek(0, SeekOrigin.Begin); string tagId; bool isValidTagHeader = false; if (contents.Equals(CONTENTS_VORBIS)) { tagId = Utils.Latin1Encoding.GetString(msr.ReadBytes(7)); isValidTagHeader = (VORBIS_TAG_ID.Equals(tagId)); } else if (contents.Equals(CONTENTS_OPUS)) { tagId = Utils.Latin1Encoding.GetString(msr.ReadBytes(8)); isValidTagHeader = (OPUS_TAG_ID.Equals(tagId)); } if (isValidTagHeader) { vorbisTag.Clear(); vorbisTag.Read(msr, readTagParams); } } } // using MemoryStream result = true; } } return(result); }
private bool readData(BinaryReader source, ReadTagParams readTagParams) { Stream fs = source.BaseStream; byte[] ID; uint objectCount; ulong headerSize, objectSize; long initialPos, position; long countPosition, sizePosition1, sizePosition2; bool result = false; if (languages != null) { languages.Clear(); } fs.Seek(sizeInfo.ID3v2Size, SeekOrigin.Begin); initialPos = fs.Position; // Check for existing header ID = source.ReadBytes(16); // Header (mandatory; one only) if (StreamUtils.ArrEqualsArr(WMA_HEADER_ID, ID)) { sizePosition1 = fs.Position; headerSize = source.ReadUInt64(); countPosition = fs.Position; objectCount = source.ReadUInt32(); fs.Seek(2, SeekOrigin.Current); // Reserved data fileData.ObjectCount = objectCount; fileData.ObjectListOffset = fs.Position; // Read all objects in header and get needed data for (int i = 0; i < objectCount; i++) { position = fs.Position; ID = source.ReadBytes(16); sizePosition2 = fs.Position; objectSize = source.ReadUInt64(); // File properties (mandatory; one only) if (StreamUtils.ArrEqualsArr(WMA_FILE_PROPERTIES_ID, ID)) { source.BaseStream.Seek(40, SeekOrigin.Current); duration = source.ReadUInt64() / 10000.0; // Play duration (100-nanoseconds) source.BaseStream.Seek(8, SeekOrigin.Current); // Send duration; unused for now duration -= source.ReadUInt64(); // Preroll duration (ms) } // Stream properties (mandatory; one per stream) else if (StreamUtils.ArrEqualsArr(WMA_STREAM_PROPERTIES_ID, ID)) { source.BaseStream.Seek(54, SeekOrigin.Current); fileData.FormatTag = source.ReadUInt16(); fileData.Channels = source.ReadUInt16(); fileData.SampleRate = source.ReadInt32(); } // Content description (optional; one only) // -> standard, pre-defined metadata else if (StreamUtils.ArrEqualsArr(WMA_CONTENT_DESCRIPTION_ID, ID) && readTagParams.ReadTag) { tagExists = true; structureHelper.AddZone(position, (int)objectSize, ZONE_CONTENT_DESCRIPTION); // Store frame information for future editing, since current frame is optional if (readTagParams.PrepareForWriting) { structureHelper.AddSize(sizePosition1, headerSize, ZONE_CONTENT_DESCRIPTION); structureHelper.AddCounter(countPosition, objectCount, ZONE_CONTENT_DESCRIPTION); } readContentDescription(source, readTagParams); } // Extended content description (optional; one only) // -> extended, dynamic metadata else if (StreamUtils.ArrEqualsArr(WMA_EXTENDED_CONTENT_DESCRIPTION_ID, ID) && readTagParams.ReadTag) { tagExists = true; structureHelper.AddZone(position, (int)objectSize, ZONE_EXTENDED_CONTENT_DESCRIPTION); // Store frame information for future editing, since current frame is optional if (readTagParams.PrepareForWriting) { structureHelper.AddSize(sizePosition1, headerSize, ZONE_EXTENDED_CONTENT_DESCRIPTION); structureHelper.AddCounter(countPosition, objectCount, ZONE_EXTENDED_CONTENT_DESCRIPTION); } readExtendedContentDescription(source, readTagParams); } // Header extension (mandatory; one only) // -> extended, dynamic additional metadata such as additional embedded pictures (any picture after the 1st one stored in extended content) else if (StreamUtils.ArrEqualsArr(WMA_HEADER_EXTENSION_ID, ID) && readTagParams.ReadTag) { readHeaderExtended(source, sizePosition1, headerSize, sizePosition2, objectSize, readTagParams); } fs.Seek(position + (long)objectSize, SeekOrigin.Begin); } // Add absent zone definitions for further editing if (readTagParams.PrepareForWriting) { if (!structureHelper.ZoneNames.Contains(ZONE_CONTENT_DESCRIPTION)) { structureHelper.AddZone(fs.Position, 0, ZONE_CONTENT_DESCRIPTION); structureHelper.AddSize(sizePosition1, headerSize, ZONE_CONTENT_DESCRIPTION); structureHelper.AddCounter(countPosition, objectCount, ZONE_CONTENT_DESCRIPTION); } if (!structureHelper.ZoneNames.Contains(ZONE_EXTENDED_CONTENT_DESCRIPTION)) { structureHelper.AddZone(fs.Position, 0, ZONE_EXTENDED_CONTENT_DESCRIPTION); structureHelper.AddSize(sizePosition1, headerSize, ZONE_EXTENDED_CONTENT_DESCRIPTION); structureHelper.AddCounter(countPosition, objectCount, ZONE_EXTENDED_CONTENT_DESCRIPTION); } } result = true; } fileData.HeaderSize = fs.Position - initialPos; return(result); }
public bool Write(BinaryReader r, BinaryWriter w, TagData tag) { bool result = true; // Constraint-check on non-supported values if (FieldCodeFixedLength > 0) { if (tag.Pictures != null) { foreach (PictureInfo picInfo in tag.Pictures) { if (PictureInfo.PIC_TYPE.Unsupported.Equals(picInfo.PicType) && (picInfo.TagType.Equals(getImplementedTagType()))) { if ((-1 == picInfo.NativePicCode) && (Utils.ProtectValue(picInfo.NativePicCodeStr).Length != FieldCodeFixedLength)) { throw new NotSupportedException("Field code fixed length is " + FieldCodeFixedLength + "; detected field '" + Utils.ProtectValue(picInfo.NativePicCodeStr) + "' is " + Utils.ProtectValue(picInfo.NativePicCodeStr).Length + " characters long and cannot be written"); } } } } foreach (MetaFieldInfo fieldInfo in tag.AdditionalFields) { if (fieldInfo.TagType.Equals(getImplementedTagType()) || MetaDataIOFactory.TAG_ANY == fieldInfo.TagType) { string fieldCode = Utils.ProtectValue(fieldInfo.NativeFieldCode); if (fieldCode.Length != FieldCodeFixedLength && !fieldCode.Contains("----")) // "----" = exception for MP4 extended fields (e.g. ----:com.apple.iTunes:CONDUCTOR) { throw new NotSupportedException("Field code fixed length is " + FieldCodeFixedLength + "; detected field '" + fieldCode + "' is " + fieldCode.Length + " characters long and cannot be written"); } } } } structureHelper.Clear(); tagData.Pictures.Clear(); // Read all the fields in the existing tag (including unsupported fields) ReadTagParams readTagParams = new ReadTagParams(true, true); readTagParams.PrepareForWriting = true; if (embedder != null && embedder.HasEmbeddedID3v2 > 0) { readTagParams.offset = embedder.HasEmbeddedID3v2; } this.read(r, readTagParams); if (embedder != null && getImplementedTagType() == MetaDataIOFactory.TAG_ID3V2) { structureHelper.Clear(); structureHelper.AddZone(embedder.Id3v2Zone); } // Give engine something to work with if the tag is really empty if (!tagExists && 0 == Zones.Count) { structureHelper.AddZone(0, 0); } TagData dataToWrite; dataToWrite = tagData; dataToWrite.IntegrateValues(tag); // Merge existing information + new tag information dataToWrite.Cleanup(); FileSurgeon surgeon = new FileSurgeon(structureHelper, embedder, getImplementedTagType(), getDefaultTagOffset()); result = surgeon.RewriteZones(w, new FileSurgeon.WriteDelegate(writeAdapter), Zones, dataToWrite, tagExists); // Update tag information without calling Read /* TODO - this implementation is too risky : * - if one of the writing operations fails, data is updated as if everything went right * - any picture slot with a markForDeletion flag is recorded as-is in the tag */ tagData = dataToWrite; return(result); }