Click here to Skip to main content
15,891,909 members
Articles / Desktop Programming / WPF

Wrap Panel Virtualization

Rate me:
Please Sign up or sign in to vote.
4.95/5 (18 votes)
2 Jan 2012CPOL2 min read 53.5K   5.6K   41  
WrapPanel doesn't support virtualization. But we can improve the performance by simulating virtualization.
using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.IO;
using Tags.ID3.ID3v2Frames;
using Tags.ID3.ID3v2Frames.TextFrames;
using Tags.ID3.ID3v2Frames.BinaryFrames;
using Tags.ID3.ID3v2Frames.OtherFrames;
using Tags.ID3.ID3v2Frames.ArrayFrames;
using Tags.ID3.ID3v2Frames.StreamFrames;
using System.Diagnostics;
using System.Reflection;

namespace Tags.ID3
{
    /// <summary>
    /// Provide a class to read and write ID3v2 information of files
    /// </summary>
    public class ID3v2
    {
        #region -> Variables <-

        private static bool _AutoTextEncoding = true; // when want to save frame use automatic text encoding
        private static TextEncodings _DefaultUnicodeEncoding = TextEncodings.UTF_16; // when use AutoTextEncoding which unicode type must use
        private bool _DropUnknown; // if true. unknown frames will not save
        private FilterCollection _Filter; // Contain Filter Frames
        private int _OriginID3Length; // ID3 Length of file on disk
        private ID3v2HeaderFlags _HeaderFlags;
        private string _FilePath; // ID3 file path        
        private FilterTypes _FilterType; //Indicate wich filter type use
        private bool _LoadLinkedFrames; // Indicate load Link frames when loading ID3 or not
        private Version _Version; // Contain ID3 version information
        private bool _HaveTag; // Indicate if current file have ID3v2 Info
        private ExceptionCollection _Errors; // Contain Errors that occured

        private Hashtable _CollectionFrames; // Contain FrameCollections for frames that can occur more than one time
        private Hashtable _SingleFrames; // contain frames that maximum can occur one time

        #endregion

        private enum CollectionIndex
        {
            Text,
            UserText,
            Private,
            TextWithLanguage,
            SynchronisedText,
            AttachedPicture,
            EncapsulatedObject,
            Popularimeter,
            AudioEncryption,
            Link,
            TermOfUse,
            DataWithSymbol,
            Unknown
        }

        /// <summary>
        /// Create new ID3v2 class for specific file
        /// </summary>
        /// <param name="FilePath">FileAddress to read ID3 information from</param>
        /// <param name="LoadData">Indicate load ID3 in constructor or not</param>
        public ID3v2(string FilePath, bool LoadData)
        {
            // ------ Set default values -----------
            _LoadLinkedFrames = true;
            _DropUnknown = false;

            _FilePath = FilePath;

            Initializer();

            if (LoadData == true)
                Load();
        }

        private void Initializer()
        {
            _Filter = new FilterCollection();

            _CollectionFrames = new Hashtable();
            _SingleFrames = new Hashtable();

            _FilterType = FilterTypes.NoFilter;
            _Errors = new ExceptionCollection();

            FrameCollection<TextFrame> TextFrames = new FrameCollection<TextFrame>(CollectionIndex.Text.ToString());
            FrameCollection<UserTextFrame> UserTextFrames = new FrameCollection<UserTextFrame>(CollectionIndex.UserText.ToString());
            FrameCollection<PrivateFrame> PrivateFrames = new FrameCollection<PrivateFrame>(CollectionIndex.Private.ToString());
            FrameCollection<TextWithLanguageFrame> TextWithLangFrames = new FrameCollection<TextWithLanguageFrame>(CollectionIndex.TextWithLanguage.ToString());
            FrameCollection<SynchronisedText> SynchronisedTextFrames = new FrameCollection<SynchronisedText>(CollectionIndex.SynchronisedText.ToString());
            FrameCollection<AttachedPictureFrame> AttachedPictureFrames = new FrameCollection<AttachedPictureFrame>(CollectionIndex.AttachedPicture.ToString());
            FrameCollection<GeneralFileFrame> EncapsulatedObjectFrames = new FrameCollection<GeneralFileFrame>(CollectionIndex.EncapsulatedObject.ToString());
            FrameCollection<PopularimeterFrame> PopularimeterFrames = new FrameCollection<PopularimeterFrame>(CollectionIndex.Popularimeter.ToString());
            FrameCollection<AudioEncryptionFrame> AudioEncryptionFrames = new FrameCollection<AudioEncryptionFrame>(CollectionIndex.AudioEncryption.ToString());
            FrameCollection<LinkFrame> LinkFrames = new FrameCollection<LinkFrame>(CollectionIndex.Link.ToString());
            FrameCollection<TermOfUseFrame> TermOfUseFrames = new FrameCollection<TermOfUseFrame>(CollectionIndex.TermOfUse.ToString());
            FrameCollection<DataWithSymbolFrame> DataWithSymbolFrames = new FrameCollection<DataWithSymbolFrame>(CollectionIndex.DataWithSymbol.ToString());
            FrameCollection<BinaryFrame> UnknownFrames = new FrameCollection<BinaryFrame>(CollectionIndex.Unknown.ToString());

            _CollectionFrames.Add(CollectionIndex.Text, TextFrames);
            _CollectionFrames.Add(CollectionIndex.UserText, UserTextFrames);
            _CollectionFrames.Add(CollectionIndex.Private, PrivateFrames);
            _CollectionFrames.Add(CollectionIndex.TextWithLanguage, TextWithLangFrames);
            _CollectionFrames.Add(CollectionIndex.SynchronisedText, SynchronisedTextFrames);
            _CollectionFrames.Add(CollectionIndex.AttachedPicture, AttachedPictureFrames);
            _CollectionFrames.Add(CollectionIndex.EncapsulatedObject, EncapsulatedObjectFrames);
            _CollectionFrames.Add(CollectionIndex.Popularimeter, PopularimeterFrames);
            _CollectionFrames.Add(CollectionIndex.AudioEncryption, AudioEncryptionFrames);
            _CollectionFrames.Add(CollectionIndex.Link, LinkFrames);
            _CollectionFrames.Add(CollectionIndex.TermOfUse, TermOfUseFrames);
            _CollectionFrames.Add(CollectionIndex.DataWithSymbol, DataWithSymbolFrames);
            _CollectionFrames.Add(CollectionIndex.Unknown, UnknownFrames);

            Version = new Version(2, 3, 0, 0);
        }

        #region -> File Name Methods <-

        private string GetFilePath(string Formula)
        {
            if (Formula == string.Empty)
                return FilePath;

            return Path.Combine(Path.GetDirectoryName(_FilePath), MakeFileName(Formula));
        }

        /// <summary>
        /// Get FileName according to specific formula
        /// </summary>
        /// <param name="Formula">Formula to make FileName</param>
        /// <returns>System.String contain FileName according to formula or String.Empty</returns>
        public string MakeFileName(string Formula)
        {
            string FileName = "";

            Formula = Formula.Replace("<", "<;");
            string ID;
            foreach (string St in Formula.Split('>', '<'))
            {
                if (St.StartsWith(";"))
                {
                    ID = St.Remove(0, 1).ToUpper();
                    if (ID.StartsWith("TRCK"))
                    {
                        string TRCK = TrackNumber;
                        if (ID.Length == 5)
                        {
                            int Digits = int.Parse(ID[4].ToString());

                            while (Digits-- > TrackNumber.Length)
                            {
                                FileName += "0";
                            }
                        }
                        FileName += TRCK;
                    }
                    else
                        FileName += GetTextFrame(ID);
                }
                else
                    FileName += St;
            }

            return FileName + ".mp3";
        }

        /// <summary>
        /// Make a temp file name for specific filename
        /// </summary>
        /// <param name="FileName">Filename to make temp name</param>
        /// <returns>string contain Temp FileName</returns>
        private string MakeTempFilePath(string FileName)
        {
            FileName += "~Temp";

            // Make sure that file name doesn't exists
            int counter = 0;
            string TempName = FileName;
            while (File.Exists(TempName))
                TempName = FileName + (counter++).ToString();

            return TempName;
        }

        #endregion

        /// <summary>
        /// Get TrackNumber for renaming
        /// </summary>
        private string TrackNumber
        {
            get
            {
                string Track = GetTextFrame("TRCK");
                int i = Track.IndexOf('/');
                if (i != -1)
                    Track = Track.Substring(0, i);
                return Track;
            }
        }

        /// <summary>
        /// Add specific ID3Error to ErrorCollection
        /// </summary>
        /// <param name="Error">Error to add</param>
        private void AddError(Exception Error)
        {
            _Errors.Add(Error);
        }

        private int OriginAudioPosition
        {
            get
            { return (_OriginID3Length > 0) ? _OriginID3Length + 11 : 0; }
        }

        private bool _IsTemplate;
        /// <summary>
        /// Indicate if current ID3v2 is template not a real file
        /// </summary>
        public bool IsTemplate
        {
            get { return _IsTemplate; }
            set { _IsTemplate = value; }
        }

        #region -> Public Properties <-

        /// <summary>
        /// Gets Collection of Errors that occured
        /// </summary>
        public ExceptionCollection Errors
        {
            get
            { return _Errors; }
        }

        /// <summary>
        /// Gets FileAddress of current ID3v2
        /// </summary>
        public string FilePath
        {
            get
            { return _FilePath; }
            private set
            { _FilePath = value; }
        }

        /// <summary>
        /// Get FileName of current ID3v2
        /// </summary>
        public string FileName
        {
            get
            { return Path.GetFileName(_FilePath); }
        }

        /// <summary>
        /// Get Filter of current frame
        /// </summary>
        public FilterCollection Filter
        {
            get
            { return _Filter; }
        }

        /// <summary>
        /// Gets or Sets current Tag filter type
        /// </summary>
        public FilterTypes FilterType
        {
            get
            { return _FilterType; }
            set
            { _FilterType = value; }
        }

        /// <summary>
        /// Indicate load Linked frames info while loading Tag
        /// </summary>
        public bool LoadLinkedFrames
        {
            get
            { return _LoadLinkedFrames; }
            set
            { _LoadLinkedFrames = value; }
        }

        /// <summary>
        /// Indicate drop unknown frame while saving ID3 or not
        /// </summary>
        public bool DropUnknowFrames
        {
            get
            { return _DropUnknown; }
            set
            { _DropUnknown = value; }
        }

        /// <summary>
        /// Get or Set version of current ID3 Tag
        /// </summary>
        public System.Version Version
        {
            get
            { return _Version; }
            set
            {
                if (value.Major != 2)
                    throw new ArgumentOutOfRangeException("Major", "Major version for this application is always 2");

                if (value.Minor < 3 || value.Minor > 4)
                    throw new ArgumentOutOfRangeException("Minor", "Minor Version for this application can be 3 or 4");

                if (value.Build > 255)
                    throw new ArgumentOutOfRangeException("Build", "Build can have maximum value of 255");

                _Version = value;
            }
        }

        /// <summary>
        /// Indicate if current file have ID3v2 Information
        /// </summary>
        public bool HaveTag
        {
            get
            { return _HaveTag; }
            set
            {
                if (_HaveTag == true && value == false)
                    ClearAll();

                _HaveTag = value;
            }
        }

        /// <summary>
        /// Get length of current ID3 Tag
        /// </summary>
        public int Length
        {
            get
            {
                int RLen = 0;
                foreach (FrameCollectionBase Coll in _CollectionFrames.Values)
                {
                    if (Coll.Name != CollectionIndex.Unknown.ToString() ||
                        (Coll.Name == CollectionIndex.Unknown.ToString() && !_DropUnknown))
                    {
                        foreach (Frame F in Coll)
                            if (F.IsValid)
                                RLen += F.Length + 10;
                    }
                }

                foreach (Frame Fr in _SingleFrames.Values)
                    if (Fr.IsValid)
                        RLen += Fr.Length + 10;

                return RLen;
            }
        }

        /// <summary>
        /// Indicate if current ID3Info had error while openning
        /// </summary>
        public bool HaveError
        {
            get
            {
                if (_Errors.Count > 0)
                    return true;
                else
                    return false;
            }
        }

        /// <summary>
        /// Gets or sets unicode encoding of AutoTextEncoding
        /// </summary>
        public static TextEncodings DefaultUnicodeEncoding
        {
            get
            { return _DefaultUnicodeEncoding; }
            set
            {
                if ((int)value > 3 || (int)value < 2)
                    throw (new ArgumentOutOfRangeException("Default unicode must be one of (UTF_16, UTF_16BE, UTF_8)"));

                _DefaultUnicodeEncoding = value;
            }
        }

        /// <summary>
        /// Indicate while saving automatically detect encoding of texts of not
        /// </summary>
        public static bool AutoTextEncoding
        {
            get
            { return _AutoTextEncoding; }
            set
            { _AutoTextEncoding = value; }
        }

        /// <summary>
        /// Indicate if current ID3v2 is experimental
        /// </summary>
        public bool Experimental
        {
            get
            { return (_HeaderFlags & ID3v2HeaderFlags.Experimental) == ID3v2HeaderFlags.Experimental; }
            set
            {
                if (value)
                    _HeaderFlags |= ID3v2HeaderFlags.Experimental;
                else
                    _HeaderFlags &= ~ID3v2HeaderFlags.Experimental;
            }
        }

        /// <summary>
        /// Indicate if current ID3v2 is Unsychronized
        /// </summary>
        public bool Unsynchronisation
        {
            get
            { return (_HeaderFlags & ID3v2HeaderFlags.Unsynchronisation) == ID3v2HeaderFlags.Unsynchronisation; }
            set
            {
                if (value)
                    _HeaderFlags |= ID3v2HeaderFlags.Unsynchronisation;
                else
                    _HeaderFlags &= ~ID3v2HeaderFlags.Unsynchronisation;
            }
        }

        /// <summary>
        /// Indicate if current ID3v2 contains Extended header
        /// </summary>
        public bool ExtendedHeader
        {
            get
            { return (_HeaderFlags & ID3v2HeaderFlags.ExtendedHeader) == ID3v2HeaderFlags.ExtendedHeader; }
        }

        #endregion

        #region -> Load Methods <-

        /// <summary>
        /// Load ID3 information from file
        /// </summary>
        /// <exception cref="FileNotFoundException">File Not Found</exception>
        public void Load()
        {
            Errors.Clear();
            ClearAll();

            TagStream ID3File = new TagStream(_FilePath, FileMode.Open);
            ReadHeader(ID3File);

            if (!HaveTag) // If file don't contain ID3v2 exit function
            {
                ID3File.Close();
                return;
            }

            ReadFrames(ID3File, _OriginID3Length);
            ID3File.Close();
        }

        /// <summary>
        /// Load all linked information frames
        /// </summary>
        public void LoadAllLinkedFrames()
        {
            foreach (LinkFrame LF in LinkFrames)
                LoadFrameFromFile(LF.FrameIdentifier, LF.URL);
        }

        /// <summary>
        /// Load spefic frame information
        /// </summary>
        /// <param name="FrameID">FrameID to load</param>
        /// <param name="FileAddress">FileAddress to read tag from</param>
        private void LoadFrameFromFile(string FrameID, string FileAddress)
        {
            ID3v2 LinkedInfo = new ID3v2(FileAddress, false);
            LinkedInfo.Filter.Add(FrameID);
            LinkedInfo.FilterType = FilterTypes.LoadFiltersOnly;
            LinkedInfo.Load();

            if (LinkedInfo.HaveError)
                foreach (ID3Exception IE in LinkedInfo.Errors)
                    _Errors.Add(new ID3Exception("In Linked Info(" +
                        FileAddress + "): " + IE.Message, IE.FrameID, IE.Level));

            foreach (FrameCollectionBase Coll in LinkedInfo._CollectionFrames)
            {
                if (Coll.Name == CollectionIndex.Link.ToString())
                {

                    continue;
                }

                foreach (Frame Fr in Coll)
                {
                    FrameCollection<Frame> Temp =
                        (FrameCollection<Frame>)_CollectionFrames[
                        Enum.Parse(typeof(CollectionIndex), Coll.Name)];

                    Temp.Add(Fr);
                }
            }

            foreach (Frame In in (Frame[])LinkedInfo._SingleFrames.Values)
            {
                if (_SingleFrames.ContainsKey(In.FrameID))
                    _SingleFrames.Remove(In);

                _SingleFrames.Add(In.FrameID, LinkedInfo._SingleFrames[In]);
            }
        }

        #endregion

        #region -> Save Methods <-

        /// <summary>
        /// Save ID3v2 data without renaming file with minor version of 3
        /// </summary>
        public void Save()
        {
            Save("");
        }

        /// <summary>
        /// Save ID3 info to file
        /// </summary>
        /// <param name="Formula">Formula to renaming file</param>
        public void Save(string Formula)
        {
            SaveAs(GetFilePath(Formula));
        }

        /// <summary>
        /// Save Current ID3v2 to specific location
        /// </summary>
        /// <param name="NewFilePath">Path to save file</param>
        public void SaveAs(string NewFilePath)
        {
            try
            {
                string TempPath = MakeTempFilePath(NewFilePath);

                using (TagStream writer = new TagStream(TempPath, FileMode.Create))
                {
                    int OriginLength;
                    if (!_HaveTag)
                    {
                        AppendFromOriginalFile(writer);
                        OriginLength = 0;
                    }
                    else
                    {
                        OriginLength = WriteHeader(writer);
                        WriteFrames(writer, Version.Minor);
                        if (!IsTemplate)
                            AppendFromOriginalFile(writer);
                    }
                    // if Orginal file and current file both don't contain ID3
                    // we don't need to do anything

                    writer.Close();

                    _OriginID3Length = OriginLength;
                }

                TagStream.DeleteRename(FilePath, TempPath, NewFilePath);
                FilePath = NewFilePath;
            }
            catch (Exception Ex)
            {
                AddError(Ex);
                throw Ex;
            }
        }

        /// <summary>
        /// Write all frames to specific TagStream
        /// </summary>
        /// <param name="writer">TagStream to write data to</param>
        /// <param name="Ver">Minor Version of ID3</param>
        private void WriteFrames(TagStream writer, int Ver)
        {
            foreach (FrameCollectionBase Coll in _CollectionFrames.Values)
                if (Coll.Name != CollectionIndex.Unknown.ToString() ||
                       (Coll.Name == CollectionIndex.Unknown.ToString() && !_DropUnknown))
                    foreach (Frame Fr in Coll)
                    {
                        // If Frame is not valid and is not UserTextFrame we ignore it
                        if (!FramesInfo.IsCompatible(Fr.FrameID, Ver) && FramesInfo.IsTextFrame(Fr.FrameID, Ver) != 2)
                        {
                            AddError(new ID3Exception("nonCompatible Frame found on Frames and will not save with file", Fr.FrameID, ExceptionLevels.Warning));
                            continue;
                        }

                        if (Fr.IsValid)
                            Fr.WriteData(writer, Ver);
                    }

            foreach (Frame Fr in _SingleFrames.Values)
                if (FramesInfo.IsCompatible(Fr.FrameID, Ver) && Fr.IsValid)
                    Fr.WriteData(writer, Ver);
        }

        /// <summary>
        /// Append a file to another from start position
        /// </summary>
        /// <param name="writer">File that data must append to it</param>
        private void AppendFromOriginalFile(FileStream writer)
        {
            if (IsTemplate)
                return;

            FileStream reader = new FileStream(FilePath, FileMode.Open);
            reader.Seek(OriginAudioPosition, SeekOrigin.Begin);

            byte[] Buf = new byte[reader.Length - OriginAudioPosition];
            reader.Read(Buf, 0, Buf.Length);
            writer.Write(Buf, 0, Buf.Length);

            reader.Close();
        }

        #endregion

        #region -> Public Methods <-

        /// <summary>
        /// Search TextFrames for specific FrameID
        /// </summary>
        /// <param name="FrameID">FrameID to search in TextFrames</param>
        /// <returns>TextFrame according to FrameID</returns>
        public string GetTextFrame(string FrameID)
        {
            foreach (TextFrame TF in TextFrames)
                if (TF.FrameID == FrameID)
                    return TF.Text;

            return "";
        }

        /// <summary>
        /// Set text of specific TextFrame
        /// </summary>
        /// <param name="FrameID">FrameID</param>
        /// <param name="Text">Text to set</param>
        public void SetTextFrame(string FrameID, string Text)
        {
            if (!FramesInfo.IsValidFrameID(FrameID))
                return;

            TextFrame[] TF = TextFrames.ToArray();
            for (int i = 0; i < TextFrames.Count - 1; i++)
            {
                if (TF[i].FrameID == FrameID)
                {
                    TextFrames.RemoveAt(i);
                    break;
                }
            }

            if (Text != "")
            {
                HaveTag = true;
                TextFrames.Add(new TextFrame(FrameID, new FrameFlags(),
                    Text, (StaticMethods.IsAscii(Text) ? TextEncodings.Ascii : _DefaultUnicodeEncoding),
                    _Version.Minor));
            }
        }

        /// <summary>
        /// Clear all ID3 Tag information
        /// </summary>
        public void ClearAll()
        {
            foreach (CollectionBase Coll in _CollectionFrames.Values)
                Coll.Clear();

            _SingleFrames.Clear();

            _HaveTag = false;
        }

        #endregion

        #region -> Private 'Read Methods' <-

        /// <summary>
        /// Read all frames from specific FileStream
        /// </summary>
        /// <param name="Data">FileStream to read data from</param>
        /// <param name="Length">Length of data to read from FileStream</param>
        private void ReadFrames(TagStream Data, int Length)
        {
            string FrameID;
            int FrameLength;
            FrameFlags Flags = new FrameFlags();
            byte Buf;
            // If ID3v2 is ID3v2.2 FrameID, FrameLength of Frames is 3 byte
            // otherwise it's 4 character
            int FrameIDLen = Version.Minor == 2 ? 3 : 4;

            // Minimum frame size is 10 because frame header is 10 byte
            while (Length > 10)
            {
                // check for padding( 00 bytes )
                Buf = Data.ReadByte();
                if (Buf == 0)
                {
                    Length--;
                    continue;
                }

                // if readed byte is not zero. it must read as FrameID
                Data.Seek(-1, SeekOrigin.Current);

                // ---------- Read Frame Header -----------------------
                FrameID = Data.ReadText(FrameIDLen, TextEncodings.Ascii);
                if (FrameIDLen == 3)
                    FrameID = FramesInfo.Get4CharID(FrameID);
                FrameLength = Convert.ToInt32(Data.ReadUInt(FrameIDLen));
                if (FrameIDLen == 4)
                    Flags = (FrameFlags)Data.ReadUInt(2);
                else
                    Flags = 0; // must set to default flag

                long Position = Data.Position;

                if (Length > 0x10000000)
                    throw (new FileLoadException("This file contain frame that have more than 256MB data. This is not valid for ID3."));

                bool Added = false;
                if (IsAddable(FrameID)) // Check if frame is not filter
                    Added = AddFrame(Data, FrameID, FrameLength, Flags);

                if (!Added)
                    // if don't read this frame
                    // we must go forward to read next frame
                    Data.Position = Position + FrameLength;

                Length -= FrameLength + 10;
                // 10 for Frame Header header
            }
        }

        /// <summary>
        /// Indicate is specific FrameID filtered or not
        /// </summary>
        /// <param name="FrameID">FrameID to check</param>
        /// <returns>true if can add otherwise false</returns>
        private bool IsAddable(string FrameID)
        {
            if (_FilterType == FilterTypes.NoFilter)
                return true;
            else if (_FilterType == FilterTypes.LoadFiltersOnly)
                return _Filter.IsExists(FrameID);
            else // Not Load Filters
                return !_Filter.IsExists(FrameID);
        }

        /// <summary>
        /// Add Frame information to where it must store
        /// </summary>
        /// <param name="Data">FileStream contain Frame</param>
        /// <param name="FrameID">FrameID of frame</param>
        /// <param name="Length">Maximum available length to read</param>
        /// <param name="Flags">Flags of frame</param>
        private bool AddFrame(TagStream Data, string FrameID, int Length, FrameFlags Flags)
        {
            // NOTE: All FrameIDs must be capital letters
            if (!FramesInfo.IsValidFrameID(FrameID))
            {
                AddError(new ID3Exception("nonValid Frame found and dropped", FrameID, ExceptionLevels.Repaired));
                return false;
            }

            int IsText = FramesInfo.IsTextFrame(FrameID, _Version.Minor);
            if (IsText == 1)
            {
                TextFrame TempTextFrame = new TextFrame(FrameID, Flags, Data, Length);
                if (TempTextFrame.IsValid)
                {
                    TextFrames.Add(TempTextFrame);
                    return true;
                }
                return false;
            }
            else if (IsText == 2)
            {
                UserTextFrame TempUserTextFrame = new UserTextFrame(FrameID, Flags, Data, Length);
                if (TempUserTextFrame.IsValid)
                {
                    UserTextFrames.Add(TempUserTextFrame);
                    return true;
                }
                return false;
            }
            else if (FrameID == "LINK")
            {
                LinkFrame LF = new LinkFrame(FrameID, Flags, Data, Length);
                if (LF.IsValid)
                {
                    LinkFrames.Add(LF);
                    if (_LoadLinkedFrames)
                    { LoadFrameFromFile(LF.FrameIdentifier, LF.URL); return true; }
                }
                else
                    AddError(LF.Exception);
            }

            Frame F;
            FrameInfo Info = FramesInfo.GetFrame(FrameID);

            if (Info == null || Info.ClassType == null)
            {
                if (!DropUnknowFrames) // Unknown frame found
                {
                    AddError(new ID3Exception("Unknown frame found try to load it", FrameID, ExceptionLevels.Warning));
                    return ReadUnknownFrame(FrameID, Flags, Data, Length);
                }
                else
                {
                    AddError(new ID3Exception("Unknown Frame found and dropped according to setting", FrameID, ExceptionLevels.Warning));
                    return true;
                }
            }

            F = Info.Constuctor(FrameID, Flags, Data, Length);
            if (F.IsValid)
            {
                if (Info.IsSingle)
                {
                    if (_SingleFrames.Contains(FrameID))
                        _SingleFrames.Remove(FrameID);

                    _SingleFrames.Add(FrameID, F);
                    return true;
                }
                else
                    foreach (FrameCollectionBase Coll in _CollectionFrames.Values)
                    {
                        if (Coll.CollectionType == Info.ClassType)
                        {
                            Coll.Remove(F);
                            Coll.Add(F);
                            return true;
                        }
                    }
                AddError(new ID3Exception("ClassType not found in Collection list", FrameID, ExceptionLevels.Error));
            }
            else if (F.Exception != null)
                AddError(F.Exception);

            return false;
        }

        private bool ReadUnknownFrame(string FrameID, FrameFlags Flags, TagStream Data, int Length)
        {
            BinaryFrame Unknown = new BinaryFrame(FrameID, Flags, Data, Length);
            if (Unknown.IsValid)
            {
                UnKnownFrames.Add(Unknown);
                return true;
            }
            else
                AddError(Unknown.Exception);
            return false;
        }

        #endregion

        #region -> Header Methods <-

        private void ReadHeader(TagStream reader)
        {
            reader.Seek(0, SeekOrigin.Begin);
            if (reader.ReadText(3, TextEncodings.Ascii) != "ID3")
            {
                _OriginID3Length = 0;
                _HaveTag = false;
                return;
            }

            ReadVersion(reader); // Read ID3 Version
            ReadHeaderFlags(reader); // Read header flags
            _OriginID3Length = ReadID3Length(reader);

            if (ExtendedHeader)
            {
                _Errors.Add(new ID3Exception("This file contain Extended Header. This application ignore Extended header", ExceptionLevels.Error));

                reader.Seek(reader.ReadUInt(4) + 6, SeekOrigin.Current); // Ignore Extended Frame
            }

            _HaveTag = true;
        }

        private void ReadVersion(TagStream reader)
        {
            Version VerInfo = new Version("2." + reader.ReadByte().ToString() + "." +
                reader.ReadByte().ToString());

            if (VerInfo.Minor > 4)
                _Errors.Add(new ID3Exception("ID3v" + _Version.ToString() +
                    " is higher than this application supporting," +
                    "but try to load it", ExceptionLevels.Warning));

            if (VerInfo.Minor < 2)
                throw new NotSupportedException("ID3v" + _Version.ToString() +
                    " is not supported by this application");

            _Version = VerInfo;
        }

        private void ReadHeaderFlags(TagStream reader)
        {
            _HeaderFlags = (ID3v2HeaderFlags)reader.ReadByte();

            if ((_HeaderFlags & ID3v2HeaderFlags.Experimental) == ID3v2HeaderFlags.Experimental)
                _Errors.Add(new ID3Exception("This file contain Experimental ID3", ExceptionLevels.Warning));
        }

        private int WriteHeader(TagStream writer)
        {
            writer.WriteText("ID3", TextEncodings.Ascii, false);

            writer.WriteByte(Convert.ToByte(_Version.Minor));
            writer.WriteByte(Convert.ToByte(_Version.Build));

            writer.WriteByte((byte)_HeaderFlags);

            byte[] Buf = new byte[4];
            int Len = Length;
            int TOrginLength = Len;
            for (int i = 3; i >= 0; i--)
            {
                Buf[i] = Convert.ToByte(Len % 0x80);
                Len /= 0x80;
            }
            writer.Write(Buf, 0, 4);

            return TOrginLength;
        }

        private int ReadID3Length(TagStream reader)
        {
            /* ID3 Size is like:
             * 0XXXXXXXb 0XXXXXXXb 0XXXXXXXb 0XXXXXXXb (b means binary)
             * the zero bytes must ignore, so we have 28 bits number = 0x1000 0000 (maximum)
             * it's equal to 256MB
             */
            int RInt = 0;
            int Mul;
            byte Buf;
            for (Mul = 0x200000; Mul >= 1; Mul /= 0x80)
            {
                Buf = reader.ReadByte();
                if (Buf > 0x80)
                    throw new DataMisalignedException(Buf.ToString() + " is invalid for ID3 byte size");

                RInt += Buf * Mul;
            }

            return RInt;
        }

        #endregion

        #region -> Public 'Single Time Frames Properties' <-

        private void SetSingleValue(string Key, Frame Value)
        {
            if (Value == null)
                _SingleFrames.Remove(Key);
            else
            {
                if (_SingleFrames.ContainsKey(Key))
                    _SingleFrames[Key] = Value;
                else
                    _SingleFrames.Add(Key, Value);
            }
        }

        private Frame GetSingleValue(string Key)
        {
            return (Frame)_SingleFrames[Key];
        }

        /// <summary>
        /// Get MusicCDIdentifier of current ID3
        /// </summary>
        public BinaryFrame MusicCDIdentifier
        {
            get
            { return (BinaryFrame)_SingleFrames["MCDI"]; }
            set
            { SetSingleValue("MCDI", value); }
        }

        /// <summary>
        /// Get SynchronisedTempoCodes of current ID3
        /// </summary>
        public SynchronisedTempoFrame SynchronisedTempoCodes
        {
            get
            { return (SynchronisedTempoFrame)_SingleFrames["SYTC"]; }
            set
            { SetSingleValue("SYTC", value); }
        }

        /// <summary>
        /// Get PlayCounter of current ID3
        /// </summary>
        public PlayCounterFrame PlayCounter
        {
            get
            { return (PlayCounterFrame)_SingleFrames["PCNT"]; }
            set
            { SetSingleValue("PCNT", value); }
        }

        /// <summary>
        /// Get RecomendedBuffer of current ID3
        /// </summary>
        public RecomendedBufferSizeFrame RecomendedBuffer
        {
            get
            { return (RecomendedBufferSizeFrame)_SingleFrames["RBUF"]; }
            set
            { SetSingleValue("RBUF", value); }
        }

        /// <summary>
        /// Get OwnerShip of current ID3
        /// </summary>
        public OwnershipFrame OwnerShip
        {
            get
            { return (OwnershipFrame)_SingleFrames["OWNE"]; }
            set
            { SetSingleValue("OWNE", value); }
        }

        /// <summary>
        /// Get Commercial of current ID3
        /// </summary>
        public CommercialFrame Commercial
        {
            get
            { return (CommercialFrame)_SingleFrames["COMR"]; }
            set
            { SetSingleValue("COMR", value); }
        }

        /// <summary>
        /// Get Reverb of current ID3
        /// </summary>
        public ReverbFrame Reverb
        {
            get
            { return (ReverbFrame)_SingleFrames["RVRB"]; }
            set
            { SetSingleValue("RVRB", value); }
        }

        /// <summary>
        /// Get Equalisations of current ID3
        /// </summary>
        public Equalisation Equalisations
        {
            get
            { return (Equalisation)_SingleFrames["EQUA"]; }
            set
            { SetSingleValue("EQUA", value); }
        }

        /// <summary>
        /// Get RelativeVolume of current ID3
        /// </summary>
        public RelativeVolumeFrame RelativeVolume
        {
            get
            { return (RelativeVolumeFrame)_SingleFrames["RVAD"]; }
            set
            { SetSingleValue("RVAD", value); }
        }

        /// <summary>
        /// Get EventTimingCode of current ID3
        /// </summary>
        public EventTimingCodeFrame EventTimingCode
        {
            get
            { return (EventTimingCodeFrame)_SingleFrames["ETCO"]; }
            set
            { SetSingleValue("ETCO", value); }
        }

        /// <summary>
        /// Get PositionSynchronised of current ID3
        /// </summary>
        public PositionSynchronisedFrame PositionSynchronised
        {
            get
            { return (PositionSynchronisedFrame)_SingleFrames["POSS"]; }
            set
            { SetSingleValue("POSS", value); }
        }

        #endregion

        #region -> Public 'Collection Properties' <-

        /// <summary>
        /// Get TextFrame Collection of current ID3
        /// </summary>
        public FrameCollection<TextFrame> TextFrames
        {
            get
            { return (FrameCollection<TextFrame>)_CollectionFrames[CollectionIndex.Text]; }
        }

        /// <summary>
        /// Get UserTextFrame Collection of current ID3
        /// </summary>
        public FrameCollection<UserTextFrame> UserTextFrames
        {
            get { return (FrameCollection<UserTextFrame>)_CollectionFrames[CollectionIndex.UserText]; }
        }

        /// <summary>
        /// Get PrivateFrame Collection of current ID3
        /// </summary>
        public FrameCollection<PrivateFrame> PrivateFrames
        {
            get { return (FrameCollection<PrivateFrame>)_CollectionFrames[CollectionIndex.Private]; }
        }

        /// <summary>
        /// Get TextWithLanguageFrame Collection of current ID3
        /// </summary>
        public FrameCollection<TextWithLanguageFrame> TextWithLanguageFrames
        {
            get { return (FrameCollection<TextWithLanguageFrame>)_CollectionFrames[CollectionIndex.TextWithLanguage]; }
        }

        /// <summary>
        /// Get SynchronisedText Collection of current ID3
        /// </summary>
        public FrameCollection<SynchronisedText> SynchronisedTextFrames
        {
            get { return (FrameCollection<SynchronisedText>)_CollectionFrames[CollectionIndex.SynchronisedText]; }
        }

        /// <summary>
        /// Get AttachedPictureFrame Collection of current ID3
        /// </summary>
        public FrameCollection<AttachedPictureFrame> AttachedPictureFrames
        {
            get { return (FrameCollection<AttachedPictureFrame>)_CollectionFrames[CollectionIndex.AttachedPicture]; }
        }

        /// <summary>
        /// Get GeneralFileFrame Collection of current ID3
        /// </summary>
        public FrameCollection<GeneralFileFrame> EncapsulatedObjectFrames
        {
            get { return (FrameCollection<GeneralFileFrame>)_CollectionFrames[CollectionIndex.EncapsulatedObject]; }
        }

        /// <summary>
        /// Get PopularimeterFrame Collection of current ID3
        /// </summary>
        public FrameCollection<PopularimeterFrame> PopularimeterFrames
        {
            get { return (FrameCollection<PopularimeterFrame>)_CollectionFrames[CollectionIndex.Popularimeter]; }
        }

        /// <summary>
        /// Get AudioEncryptionFrame Collection of current ID3
        /// </summary>
        public FrameCollection<AudioEncryptionFrame> AudioEncryptionFrames
        {
            get { return (FrameCollection<AudioEncryptionFrame>)_CollectionFrames[CollectionIndex.AudioEncryption]; }
        }

        /// <summary>
        /// Get LinkFrame Collection of current ID3
        /// </summary>
        public FrameCollection<LinkFrame> LinkFrames
        {
            get { return (FrameCollection<LinkFrame>)_CollectionFrames[CollectionIndex.Link]; }
        }

        /// <summary>
        /// Get TermOfUseFrame Collection of current ID3
        /// </summary>
        public FrameCollection<TermOfUseFrame> TermOfUseFrames
        {
            get { return (FrameCollection<TermOfUseFrame>)_CollectionFrames[CollectionIndex.TermOfUse]; }
        }

        /// <summary>
        /// Get DataWithSymbolFrame Collection of current ID3
        /// </summary>
        public FrameCollection<DataWithSymbolFrame> DataWithSymbolFrames
        {
            get { return (FrameCollection<DataWithSymbolFrame>)_CollectionFrames[CollectionIndex.DataWithSymbol]; }
        }

        /// <summary>
        /// Get BinaryFrame Collection of current ID3
        /// </summary>
        public FrameCollection<BinaryFrame> UnKnownFrames
        {
            get { return (FrameCollection<BinaryFrame>)_CollectionFrames[CollectionIndex.Unknown]; }
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) KAZ Software Limited
Bangladesh Bangladesh
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions