private string readPSFLine(Stream source, Encoding encoding) { long lineStart = source.Position; long lineEnd; bool hasEOL = true; if (StreamUtils.FindSequence(source, new byte[1] { LINE_FEED })) { lineEnd = source.Position; } else { lineEnd = source.Length; hasEOL = false; } source.Seek(lineStart, SeekOrigin.Begin); byte[] data = new byte[lineEnd - lineStart]; source.Read(data, 0, data.Length); for (int i = 0; i < data.Length; i++) { if (data[i] < SPACE) { data[i] = SPACE; // According to spec : "All characters 0x01-0x20 are considered whitespace" } } return(encoding.GetString(data, 0, data.Length - (hasEOL?1:0)).Trim()); // -1 because we don't want to include LINE_FEED in the result }
private void audio_X_AAC_MP4_Atom(string atom, string atomCaption = null) { if (null == atomCaption) { atomCaption = atom; } ArrayLogger log = new ArrayLogger(); string resource = "AAC/mp4.m4a"; string location = TestUtils.GetResourceLocationRoot() + resource; string testFileLocation = TestUtils.GetTempTestFile(resource); using (FileStream fs = new FileStream(testFileLocation, FileMode.Open, FileAccess.ReadWrite, FileShare.Read)) { StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes(atom)); fs.Seek(-1, SeekOrigin.Current); fs.WriteByte(0); Track theTrack = new Track(fs, ".mp4"); IList <LogItem> logItems = log.GetAllItems(Log.LV_ERROR); Assert.AreEqual(1, logItems.Count); Assert.AreEqual(atomCaption + " atom could not be found; aborting read", logItems[0].Message); } // Get rid of the working copy File.Delete(testFileLocation); }
private void checkTrackDiscZeroes(FileStream fs) { using (BinaryReader r = new BinaryReader(fs)) { byte[] bytes = new byte[20]; fs.Seek(0, SeekOrigin.Begin); Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("TRACKNUMBER="))); String s = StreamUtils.ReadNullTerminatedString(r, System.Text.Encoding.ASCII); Assert.AreEqual("06", s.Substring(0, s.Length - 1)); fs.Seek(0, SeekOrigin.Begin); Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("TRACKTOTAL="))); s = StreamUtils.ReadNullTerminatedString(r, System.Text.Encoding.ASCII); Assert.AreEqual("06", s.Substring(0, s.Length - 1)); fs.Seek(0, SeekOrigin.Begin); Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("DISCNUMBER="))); s = StreamUtils.ReadNullTerminatedString(r, System.Text.Encoding.ASCII); Assert.AreEqual("03", s.Substring(0, s.Length - 1)); fs.Seek(0, SeekOrigin.Begin); Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("DISCTOTAL="))); s = StreamUtils.ReadNullTerminatedString(r, System.Text.Encoding.ASCII); Assert.AreEqual("04", s.Substring(0, s.Length - 1)); } }
public void TagIO_RW_ID3v2_CommentFields() { ConsoleLogger log = new ConsoleLogger(); // Source : MP3 with existing tag incl. comment fields (iTunNORM, iTunPGAP) String testFileLocation = TestUtils.GetTempTestFile("MP3/id3v2.2_iTunNORM-iTunPGAP.mp3"); AudioDataManager theFile = new AudioDataManager(AudioData.AudioDataIOFactory.GetInstance().GetFromPath(testFileLocation)); // Check if the two fields are indeed accessible Assert.IsTrue(theFile.ReadFromFile(false, true)); Assert.IsNotNull(theFile.ID3v2); Assert.IsTrue(theFile.ID3v2.Exists); Assert.AreEqual(4, theFile.ID3v2.AdditionalFields.Count); int found = 0; foreach (KeyValuePair <string, string> field in theFile.ID3v2.AdditionalFields) { if (field.Key.Equals("iTunNORM")) { Assert.AreEqual(" 00000099 000000A2 000002F0 000002F4 0000002E 0000002E 00002E6E 00002C5C 00000017 00000017", field.Value); // Why an empty space at the beginning ?! found++; } else if (field.Key.Equals("iTunPGAP")) { Assert.AreEqual("1", field.Value); found++; } } Assert.AreEqual(2, found); // Check if they are persisted as comment fields when editing the tag TagData theTag = new TagData(); Assert.IsTrue(theFile.UpdateTagInFile(theTag, MetaDataIOFactory.TAG_ID3V2)); // For this we need to open the file in binary mode and check that the two fields belong to a comment field byte[] readBytes = new byte[4]; byte[] expected = Utils.Latin1Encoding.GetBytes("COMM"); using (FileStream fs = new FileStream(testFileLocation, FileMode.Open, FileAccess.Read)) { Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("iTunNORM"))); fs.Seek(-22, SeekOrigin.Current); fs.Read(readBytes, 0, 4); Assert.IsTrue(StreamUtils.ArrEqualsArr(expected, readBytes)); fs.Seek(0, SeekOrigin.Begin); Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("iTunPGAP"))); fs.Seek(-22, SeekOrigin.Current); fs.Read(readBytes, 0, 4); Assert.IsTrue(StreamUtils.ArrEqualsArr(expected, readBytes)); } // Get rid of the working copy File.Delete(testFileLocation); }
/// Mandatory override of PlaylistIO.getFiles protected override void getFiles(FileStream fs, IList <string> result) { while (StreamUtils.FindSequence(fs, FILE_IDENTIFIER)) { string filePath = StreamUtils.ReadNullTerminatedString(fs, UTF8_NO_BOM); result.Add(decodeLocation(filePath)); } }
}; // "file://" protected override void getFiles(FileStream fs, IList <string> result) { string filePath; string playlistPath = System.IO.Path.GetDirectoryName(fs.Name) + System.IO.Path.DirectorySeparatorChar; while (StreamUtils.FindSequence(fs, FILE_IDENTIFIER)) { filePath = StreamUtils.ReadNullTerminatedString(fs, Encoding.UTF8); if (!System.IO.Path.IsPathRooted(filePath)) { filePath = playlistPath + filePath; } result.Add(filePath); } }
private void checkTrackDiscZeroes(FileStream fs) { using (BinaryReader r = new BinaryReader(fs)) { byte[] bytes = new byte[20]; fs.Seek(0, SeekOrigin.Begin); Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("TPOS"))); fs.Seek(7, SeekOrigin.Current); String s = StreamUtils.ReadNullTerminatedString(r, System.Text.Encoding.ASCII); Assert.AreEqual("03/04", s); fs.Seek(0, SeekOrigin.Begin); Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("TRCK"))); fs.Seek(7, SeekOrigin.Current); s = StreamUtils.ReadNullTerminatedString(r, System.Text.Encoding.ASCII); Assert.AreEqual("06/06", s); } }
private void audio_X_MP4_Atom(string resourceName, string atom, int logLevel = Log.LV_ERROR, string atomCaption = null) { if (null == atomCaption) { atomCaption = atom; } ArrayLogger log = new ArrayLogger(); string resource = "MP4/" + resourceName; string testFileLocation = TestUtils.CopyAsTempTestFile(resource); using (FileStream fs = new FileStream(testFileLocation, FileMode.Open, FileAccess.ReadWrite, FileShare.Read)) { StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes(atom)); fs.Seek(-1, SeekOrigin.Current); fs.WriteByte(0); if (StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes(atom))) { fs.Seek(-1, SeekOrigin.Current); fs.WriteByte(0); } new Track(fs, ".mp4"); IList <LogItem> logItems = log.GetAllItems(logLevel); Assert.IsTrue(logItems.Count > 0); bool found = false; foreach (LogItem l in logItems) { if (l.Message.Contains(atomCaption + " atom could not be found")) { found = true; } } Assert.IsTrue(found); } // Get rid of the working copy if (Settings.DeleteAfterSuccess) { File.Delete(testFileLocation); } }
public void TagIO_RW_ID3v2_UrlFrames() { ConsoleLogger log = new ConsoleLogger(); // Source : MP3 with existing tag incl. chapters String testFileLocation = TestUtils.GetTempTestFile("MP3/chapters.mp3"); AudioDataManager theFile = new AudioDataManager(AudioData.AudioDataIOFactory.GetInstance().GetFromPath(testFileLocation)); // Check if the two fields are indeed accessible Assert.IsTrue(theFile.ReadFromFile(false, true)); Assert.IsNotNull(theFile.ID3v2); Assert.IsTrue(theFile.ID3v2.Exists); Assert.IsTrue(theFile.ID3v2.AdditionalFields.ContainsKey("WPUB")); Assert.AreEqual(theFile.ID3v2.AdditionalFields["WPUB"], "http://auphonic.com/"); // Check if URLs are persisted properly, i.e. without encoding byte TagData theTag = new TagData(); Assert.IsTrue(theFile.UpdateTagInFile(theTag, MetaDataIOFactory.TAG_ID3V2)); Assert.IsTrue(theFile.ReadFromFile(false, true)); // 1/ Check value through ATL Assert.IsTrue(theFile.ID3v2.AdditionalFields.ContainsKey("WPUB")); Assert.AreEqual(theFile.ID3v2.AdditionalFields["WPUB"], "http://auphonic.com/"); // 2/ Check absence of encoding field in the file itself byte[] readBytes = new byte[4]; byte[] expected = Utils.Latin1Encoding.GetBytes("WPUB"); using (FileStream fs = new FileStream(testFileLocation, FileMode.Open, FileAccess.Read)) { Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("WPUB"))); fs.Seek(6, SeekOrigin.Current); Assert.IsTrue(fs.ReadByte() > 10); // i.e. byte is not a 'text encoding descriptor' } // Get rid of the working copy File.Delete(testFileLocation); }
public void StreamUtils_FindSequence() { string sequence1 = "ftypmp42"; string sequence2 = "trak"; string sequence3 = "jstsd"; using (FileStream fs = new FileStream(TestUtils.GetResourceLocationRoot() + "AAC/mp4.m4a", FileMode.Open, FileAccess.Read)) { Assert.AreEqual(true, StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes(sequence1))); Assert.AreEqual(12, fs.Position); Assert.AreEqual(true, StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes(sequence2))); Assert.AreEqual(156, fs.Position); Assert.AreEqual(true, StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes(sequence3))); Assert.AreEqual(416, fs.Position); fs.Seek(20, SeekOrigin.Begin); Assert.AreEqual(false, StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes(sequence3), 100)); } }
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); }
// --------------------------------------------------------------------------- // Read total samples of OGG file, which are located on the very last page of the file private ulong getSamples(BufferedBinaryReader source) { OggHeader header = new OggHeader(); string headerId; byte typeFlag; byte[] lacingValues = new byte[255]; byte nbLacingValues = 0; long nextPageOffset = 0; // TODO - fine tune seekSize value int seekSize = (int)Math.Round(MAX_PAGE_SIZE * 0.75); if (seekSize > source.Length) { seekSize = (int)Math.Round(source.Length * 0.5); } source.Seek(-seekSize, SeekOrigin.End); if (!StreamUtils.FindSequence(source, Utils.Latin1Encoding.GetBytes(OGG_PAGE_ID))) { LogDelegator.GetLogDelegate()(Log.LV_ERROR, "No OGG header found; aborting read operation"); // Throw exception ? return(0); } source.Seek(-4, SeekOrigin.Current); // Iterate until last page is encountered do { if (source.Position + nextPageOffset + 27 > source.Length) // End of stream about to be reached => last OGG header did not have the proper type flag { break; } source.Seek(nextPageOffset, SeekOrigin.Current); headerId = Utils.Latin1Encoding.GetString(source.ReadBytes(4)); if (headerId.Equals(OGG_PAGE_ID)) { source.Seek(1, SeekOrigin.Current); typeFlag = source.ReadByte(); source.Seek(20, SeekOrigin.Current); nbLacingValues = source.ReadByte(); nextPageOffset = 0; source.Read(lacingValues, 0, nbLacingValues); for (int i = 0; i < nbLacingValues; i++) { nextPageOffset += lacingValues[i]; } } else { LogDelegator.GetLogDelegate()(Log.LV_ERROR, "Invalid OGG header found while looking for total samples; aborting read operation"); // Throw exception ? return(0); } } while (0 == (typeFlag & 0x04)); // 0x04 marks the last page of the logical bitstream // Stream is positioned at the end of the last page header; backtracking to read AbsolutePosition field source.Seek(-nbLacingValues - 21, SeekOrigin.Current); return(source.ReadUInt64()); }
/// <inheritdoc/> protected override bool read(BinaryReader source, MetaDataIO.ReadTagParams readTagParams) { byte[] header; string trigger; IList <MidiTrack> tracks = new List <MidiTrack>(); resetData(); // Ignores everything (comments) written before the MIDI header StreamUtils.FindSequence(source.BaseStream, Utils.Latin1Encoding.GetBytes(MIDI_FILE_HEADER)); // Ready to read header data... header = source.ReadBytes(10); if ((header[0] != 0) || (header[1] != 0) || (header[2] != 0) || (header[3] != 6) ) { Logging.LogDelegator.GetLogDelegate()(Log.LV_ERROR, "Wrong MIDI header"); return(false); } type = header[5]; // MIDI STRUCTURE TYPE // 0 - single-track // 1 - multiple tracks, synchronous // 2 - multiple tracks, asynchronous if (type > 1) { Logging.LogDelegator.GetLogDelegate()(Log.LV_WARNING, "SMF type 2 MIDI files are partially supported; results may be approximate"); } tagExists = true; timebase = (header[8] << 8) + header[9]; tempo = 0; // maybe (hopefully!) overwritten by parseTrack int trackSize; int nbTrack = 0; comment = new StringBuilder(""); AudioDataOffset = source.BaseStream.Position; AudioDataSize = sizeInfo.FileSize - AudioDataOffset; // Ready to read track data... while (source.BaseStream.Position < sizeInfo.FileSize - 4) { trigger = Utils.Latin1Encoding.GetString(source.ReadBytes(4)); if (trigger != MIDI_TRACK_HEADER) { source.BaseStream.Seek(-3, SeekOrigin.Current); if (!StreamUtils.FindSequence(source.BaseStream, Utils.Latin1Encoding.GetBytes(MIDI_TRACK_HEADER))) { break; } } // trackSize is stored in big endian -> needs inverting trackSize = StreamUtils.DecodeBEInt32(source.ReadBytes(4)); tracks.Add(parseTrack(source.ReadBytes(trackSize), nbTrack)); nbTrack++; } this.tracks = tracks; if (comment.Length > 0) { comment.Remove(comment.Length - 1, 1); } tagData.IntegrateValue(TagData.TAG_FIELD_COMMENT, comment.ToString()); duration = getDuration(); bitrate = (double)sizeInfo.FileSize / duration; return(true); }
public void TagIO_RW_ID3v2_Chapters_CTOCEdgeCases() { ConsoleLogger log = new ConsoleLogger(); // Source : empty MP3 String testFileLocation = TestUtils.GetTempTestFile(emptyFile); AudioDataManager theFile = new AudioDataManager(AudioData.AudioDataIOFactory.GetInstance().GetFromPath(testFileLocation)); // Case 1. Setting Track.ChaptersTableDescription alone without setting any chapter shouldn't write any CTOC frame Assert.IsTrue(theFile.ReadFromFile(true, true)); Assert.IsNotNull(theFile.ID3v2); Assert.IsFalse(theFile.ID3v2.Exists); TagData theTag = new TagData(); theTag.ChaptersTableDescription = "aaa"; theTag.Chapters = new List <ChapterInfo>(); // Check if they are persisted properly Assert.IsTrue(theFile.UpdateTagInFile(theTag, MetaDataIOFactory.TAG_ID3V2)); Assert.IsTrue(theFile.ReadFromFile(true, true)); Assert.IsNotNull(theFile.ID3v2); Assert.IsFalse(theFile.ID3v2.Exists); // Read the file itself and check that no CTOC frame has been written using (FileStream fs = new FileStream(testFileLocation, FileMode.Open)) { Assert.IsFalse(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("CTOC"))); } //Case 2. If Settings.ID3v2_alwaysWriteCTOCFrame to true and at least 1 chapter without setting Track.ChaptersTableDescription shouldn't write any TIT2 subframe // Set a chapter but no description ChapterInfo ch = new ChapterInfo(); ch.StartTime = 123; ch.StartOffset = 456; ch.EndTime = 789; ch.EndOffset = 101112; ch.UniqueID = ""; ch.Subtitle = "bbb"; theTag.ChaptersTableDescription = ""; theTag.Chapters.Add(ch); // Check if they are persisted properly Assert.IsTrue(theFile.UpdateTagInFile(theTag, MetaDataIOFactory.TAG_ID3V2)); Assert.IsTrue(theFile.ReadFromFile(true, true)); Assert.IsNotNull(theFile.ID3v2); Assert.IsTrue(theFile.ID3v2.Exists); // Read the file itself using (FileStream fs = new FileStream(testFileLocation, FileMode.Open)) { Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("CTOC"))); Assert.IsFalse(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("TIT2"))); } // Get rid of the working copy File.Delete(testFileLocation); }
public void TagIO_RW_ID3v2_FieldCodev22Tov24() { ConsoleLogger log = new ConsoleLogger(); // Source : MP3 with existing unsupported fields : RVA & TBP String testFileLocation = TestUtils.GetTempTestFile("MP3/id3v2.2_iTunNORM-iTunPGAP.mp3"); AudioDataManager theFile = new AudioDataManager(AudioData.AudioDataIOFactory.GetInstance().GetFromPath(testFileLocation)); // Check if the two fields are indeed accessible Assert.IsTrue(theFile.ReadFromFile(false, true)); Assert.IsNotNull(theFile.ID3v2); Assert.IsTrue(theFile.ID3v2.Exists); Assert.AreEqual("1997", theFile.ID3v2.Year); int found = 0; string rvaValue = ""; string tbpValue = ""; foreach (KeyValuePair <string, string> field in theFile.ID3v2.AdditionalFields) { if (field.Key.Equals("RVA")) { rvaValue = field.Value; found++; } else if (field.Key.Equals("TBP")) { tbpValue = field.Value; found++; } } Assert.AreEqual(2, found); // Check if they are persisted with proper ID3v2.4 field codes when editing the tag TagData theTag = new TagData(); Assert.IsTrue(theFile.UpdateTagInFile(theTag, MetaDataIOFactory.TAG_ID3V2)); Assert.IsTrue(theFile.ReadFromFile(false, true)); // 1/ Check if values are the same found = 0; foreach (KeyValuePair <string, string> field in theFile.ID3v2.AdditionalFields) { if (field.Key.Equals("RVA2")) { Assert.AreEqual(rvaValue, field.Value); found++; } else if (field.Key.Equals("TBPM")) { Assert.AreEqual(tbpValue, field.Value); found++; } } Assert.AreEqual(2, found); Assert.AreEqual("1997", theFile.ID3v2.Year); // 2/ Check if they are indeed persisted as "classic" ID3v2 fields, and not as sub-codes inside a TXXX field byte[] readBytes = new byte[4]; byte[] expected = Utils.Latin1Encoding.GetBytes("TXXX"); using (FileStream fs = new FileStream(testFileLocation, FileMode.Open, FileAccess.Read)) { Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("RVA2"))); fs.Seek(-15, SeekOrigin.Current); fs.Read(readBytes, 0, 4); Assert.IsFalse(StreamUtils.ArrEqualsArr(expected, readBytes)); fs.Seek(0, SeekOrigin.Begin); expected = Utils.Latin1Encoding.GetBytes("TDRC"); Assert.IsTrue(StreamUtils.FindSequence(fs, Utils.Latin1Encoding.GetBytes("1997"))); fs.Seek(-15, SeekOrigin.Current); fs.Read(readBytes, 0, 4); Assert.IsTrue(StreamUtils.ArrEqualsArr(expected, readBytes)); } // Get rid of the working copy File.Delete(testFileLocation); }
public static void FromStream(Stream source, MetaDataIO meta, ReadTagParams readTagParams) { string str; byte[] data = new byte[256]; // Description source.Read(data, 0, 256); str = Utils.StripEndingZeroChars(Utils.Latin1Encoding.GetString(data).Trim()); if (str.Length > 0) { meta.SetMetaField("bext.description", str, readTagParams.ReadAllMetaFrames); } // Originator source.Read(data, 0, 32); str = Utils.StripEndingZeroChars(Utils.Latin1Encoding.GetString(data, 0, 32).Trim()); if (str.Length > 0) { meta.SetMetaField("bext.originator", str, readTagParams.ReadAllMetaFrames); } // OriginatorReference source.Read(data, 0, 32); str = Utils.StripEndingZeroChars(Utils.Latin1Encoding.GetString(data, 0, 32).Trim()); if (str.Length > 0) { meta.SetMetaField("bext.originatorReference", str, readTagParams.ReadAllMetaFrames); } // OriginationDate source.Read(data, 0, 10); str = Utils.StripEndingZeroChars(Utils.Latin1Encoding.GetString(data, 0, 10).Trim()); if (str.Length > 0) { meta.SetMetaField("bext.originationDate", str, readTagParams.ReadAllMetaFrames); } // OriginationTime source.Read(data, 0, 8); str = Utils.StripEndingZeroChars(Utils.Latin1Encoding.GetString(data, 0, 8).Trim()); if (str.Length > 0) { meta.SetMetaField("bext.originationTime", str, readTagParams.ReadAllMetaFrames); } // TimeReference source.Read(data, 0, 8); ulong timeReference = StreamUtils.DecodeUInt64(data); meta.SetMetaField("bext.timeReference", timeReference.ToString(), readTagParams.ReadAllMetaFrames); // BEXT version source.Read(data, 0, 2); int intData = StreamUtils.DecodeUInt16(data); meta.SetMetaField("bext.version", intData.ToString(), readTagParams.ReadAllMetaFrames); // UMID source.Read(data, 0, 64); str = ""; int usefulLength = 32; // "basic" UMID if (data[12] > 19) { usefulLength = 64; // data[12] gives the size of remaining UMID } for (int i = 0; i < usefulLength; i++) { str = str + data[i].ToString("X2"); } meta.SetMetaField("bext.UMID", str, readTagParams.ReadAllMetaFrames); // LoudnessValue source.Read(data, 0, 2); intData = StreamUtils.DecodeInt16(data); meta.SetMetaField("bext.loudnessValue", (intData / 100.0).ToString(), readTagParams.ReadAllMetaFrames); // LoudnessRange source.Read(data, 0, 2); intData = StreamUtils.DecodeInt16(data); meta.SetMetaField("bext.loudnessRange", (intData / 100.0).ToString(), readTagParams.ReadAllMetaFrames); // MaxTruePeakLevel source.Read(data, 0, 2); intData = StreamUtils.DecodeInt16(data); meta.SetMetaField("bext.maxTruePeakLevel", (intData / 100.0).ToString(), readTagParams.ReadAllMetaFrames); // MaxMomentaryLoudness source.Read(data, 0, 2); intData = StreamUtils.DecodeInt16(data); meta.SetMetaField("bext.maxMomentaryLoudness", (intData / 100.0).ToString(), readTagParams.ReadAllMetaFrames); // MaxShortTermLoudness source.Read(data, 0, 2); intData = StreamUtils.DecodeInt16(data); meta.SetMetaField("bext.maxShortTermLoudness", (intData / 100.0).ToString(), readTagParams.ReadAllMetaFrames); // Reserved source.Seek(180, SeekOrigin.Current); // CodingHistory long initialPos = source.Position; if (StreamUtils.FindSequence(source, new byte[2] { 13, 10 } /* CR LF */)) { long endPos = source.Position - 2; source.Seek(initialPos, SeekOrigin.Begin); if (data.Length < (int)(endPos - initialPos)) { data = new byte[(int)(endPos - initialPos)]; } source.Read(data, 0, (int)(endPos - initialPos)); str = Utils.StripEndingZeroChars(Utils.Latin1Encoding.GetString(data, 0, (int)(endPos - initialPos)).Trim()); if (str.Length > 0) { meta.SetMetaField("bext.codingHistory", str, readTagParams.ReadAllMetaFrames); } } }
public static ImageProperties GetImageProperties(byte[] imageData, ImageFormat format = ImageFormat.Undefined) { ImageProperties props = new ImageProperties(); if (ImageFormat.Undefined.Equals(format)) { format = GetImageFormatFromPictureHeader(imageData); } if (format.Equals(ImageFormat.Unsupported)) { return(props); } props.NumColorsInPalette = 0; props.Format = format; using (MemoryStream s = new MemoryStream(imageData)) using (BinaryReader r = new BinaryReader(s)) { long limit = (long)Math.Round(s.Length * 0.25); // TODO - test and adjust limit switch (format) { case (ImageFormat.Tiff): bool isBigEndian = (0x4D == r.ReadByte()); s.Seek(3, SeekOrigin.Current); // Skip the rest of the signature long IFDOffset = readInt32(r, isBigEndian); s.Seek(IFDOffset, SeekOrigin.Begin); int nbIFDEntries = readInt16(r, isBigEndian); long initialPos = s.Position; int IFDtag, IFDFieldType, IFDNbValues, IFDValue; int photometricInterpretation = 0; int bitsPerSample = 0; int samplesPerPixel = 0; for (int i = 0; i < nbIFDEntries; i++) { IFDtag = readInt16(r, isBigEndian); IFDFieldType = readInt16(r, isBigEndian); IFDNbValues = readInt32(r, isBigEndian); IFDValue = readInt32(r, isBigEndian); switch (IFDtag) { // Common properties case (0x0100): props.Width = IFDValue; break; case (0x0101): props.Height = IFDValue; break; // Specific properties case (0x0106): // PhotometricInterpretation photometricInterpretation = IFDValue; if (IFDValue < 2) { props.ColorDepth = 1; // Bilevel image } else if (2 == IFDValue) { props.ColorDepth = 24; // RGB full color image } // NB : A value of 3 would indicate a palette-color image, but has no effect here break; case (0x0102): // BitsPerSample bitsPerSample = IFDValue; break; case (0x0115): // SamplesPerPixel samplesPerPixel = IFDValue; break; } } if (photometricInterpretation < 2) // Bilevel { props.ColorDepth = bitsPerSample; } else if (2 == photometricInterpretation) // RGB { props.ColorDepth = 8 * samplesPerPixel; } else if (3 == photometricInterpretation) // Palette { props.ColorDepth = 8 * samplesPerPixel; props.NumColorsInPalette = bitsPerSample; } break; case (ImageFormat.Gif): byte[] GraphicControlExtensionBlockSignature = new byte[2] { 0x21, 0xf9 }; props.ColorDepth = 24; // 1 byte for each component s.Seek(3, SeekOrigin.Current); // Skip GIF signature string version = Utils.Latin1Encoding.GetString(r.ReadBytes(3)); s.Seek(4, SeekOrigin.Current); // Skip logical screen descriptors byte globalPaletteUse = r.ReadByte(); if (((globalPaletteUse & 0x80) >> 7) > 0) // File uses a global color palette { props.NumColorsInPalette = 2 << (globalPaletteUse & 0x07); } /* * v89a means that the first image block should follow the first graphic control extension block * (which may in turn be located after an application extension block if the GIF is animated) * * => The simplest way to get to the 1st image block is to look for the 1st * graphic control extension block, and to skip it */ if ("89a".Equals(version)) { initialPos = s.Position; if (StreamUtils.FindSequence(s, GraphicControlExtensionBlockSignature)) { s.Seek(6, SeekOrigin.Current); } else { LogDelegator.GetLogDelegate()(Log.LV_WARNING, "Invalid v89a GIF file; no graphic control extension block found"); // GIF is malformed; trying to find the image block directly s.Seek(initialPos, SeekOrigin.Begin); if (StreamUtils.FindSequence(s, new byte[1] { 0x2c })) { s.Seek(-1, SeekOrigin.Current); } } } // At this point, we should be at the very beginning of the first image block if (0x2c == r.ReadByte()) { s.Seek(4, SeekOrigin.Current); // Skip image position descriptors props.Width = r.ReadInt16(); props.Height = r.ReadInt16(); // No global palette is set => try and find information in the local palette of the 1st image block if (0 == props.NumColorsInPalette) { props.NumColorsInPalette = (int)Math.Pow(2, ((globalPaletteUse & 0x0F) << 4) + 1); } } else { LogDelegator.GetLogDelegate()(Log.LV_WARNING, "Error parsing GIF file; image block not found"); } break; case (ImageFormat.Bmp): // Skip useless information s.Seek(18, SeekOrigin.Begin); props.Width = r.ReadInt32(); props.Height = r.ReadInt32(); s.Seek(2, SeekOrigin.Current); // Planes props.ColorDepth = r.ReadInt16(); // No support for BMP color palettes, as they seem to be exotic (and ATL has no use of this information) break; case (ImageFormat.Png): byte[] intData = new byte[4]; byte[] IHDRChunkSignature = Utils.Latin1Encoding.GetBytes("IHDR"); byte[] PaletteChunkSignature = Utils.Latin1Encoding.GetBytes("PLTE"); // Skip header s.Seek(8, SeekOrigin.Begin); // Scroll chunks until we find IHDR (that should be the first one to appear, but who knows...) if (0 == findPngChunk(s, IHDRChunkSignature, limit)) { LogDelegator.GetLogDelegate()(Log.LV_WARNING, "Invalid PNG file; no IHDR chunk found"); } else { // Read IHDR chunk s.Read(intData, 0, 4); props.Width = StreamUtils.DecodeBEInt32(intData); s.Read(intData, 0, 4); props.Height = StreamUtils.DecodeBEInt32(intData); props.ColorDepth = r.ReadByte(); int colorType = r.ReadByte(); if (3 == colorType) // PNG file uses a palette { s.Seek(7, SeekOrigin.Current); // 3 last useful data + ending chunk CRC uint paletteChunkSize = findPngChunk(s, PaletteChunkSignature, limit); if (0 == paletteChunkSize) { LogDelegator.GetLogDelegate()(Log.LV_WARNING, "Invalid PNG file; palette declared, but no PLTE chunk found"); } else { props.NumColorsInPalette = (int)Math.Floor(paletteChunkSize / 3.0); } } else { props.NumColorsInPalette = 0; } } break; case (ImageFormat.Jpeg): byte[] shortData = new byte[2]; byte[] SOF0FrameSignature = new byte[2] { 0xFF, 0xC0 }; /* * We just need to reach the SOF0 frame descripting the actual picture * * In order to handle JPEG files that contain multiple SOF0 frames (see test suite), * the simplest way of proceeding is to look for all SOF0 frames in the first 25% of the file, * and then read the very last one */ long lastPos = 0; while (StreamUtils.FindSequence(s, SOF0FrameSignature, limit)) { lastPos = s.Position; } if (0 == lastPos) { LogDelegator.GetLogDelegate()(Log.LV_WARNING, "Invalid JPEG file; no SOF0 frame found"); } else { // Skip frame length s.Seek(2, SeekOrigin.Current); bitsPerSample = r.ReadByte(); s.Read(shortData, 0, 2); props.Height = StreamUtils.DecodeBEUInt16(shortData); s.Read(shortData, 0, 2); props.Width = StreamUtils.DecodeBEUInt16(shortData); byte nbComponents = r.ReadByte(); props.ColorDepth = bitsPerSample * nbComponents; } break; } } return(props); }