Sylloge
A C# helper library
code/Audio/Metadata/ID3/Frame.cs
Go to the documentation of this file.
00001 using System;
00002 using System.Collections.Generic;
00003 using System.Linq;
00004 using System.Text;
00005 
00006 namespace Sylloge.Audio.Metadata.ID3
00007 {
00011     public class Frame
00012     {
00013         //ID3 tag headers follow a specific format:
00014         //Tag ID: xx xx xx xx (four characters) 
00015         //Size:   xx xx xx xx
00016         //Flags:  xx xx
00017 
00018         #region Keys, Classes and Constants
00019 
00023         public const byte EOL = (byte)0;
00024 
00029         public static class Keys
00030         {
00034             public const string AttachedPicture = "APIC";
00038             public const string Comment = "COMM";
00042             public const string Album = "TALB";
00046             public const string Composer = "TCOM";
00050             public const string Genre = "TCON";
00054             public const string CopyrightInfo = "TCOP";
00058             public const string EncodedBy = "TENC";
00062             public const string TitleOfSong = "TIT2";
00066             public const string OriginalArtist = "TOPE";
00070             public const string Artist = "TPE1";
00074             public const string AlbumArtist = "TPE2";
00078             public const string DiscNumber = "TPOS";
00082             public const string Publisher = "TPUB";
00086             public const string Track = "TRCK";
00090             public const string Year = "TYER";
00094             public const string UserDefinedString = "TXXX";
00098             public const string UserDefinedURL = "WXXX";
00099 
00100 
00104             public static string[] Supported { get { return Keys.m_sup.ToArray(); } }
00105 
00109             public static string[] Reserved { get { return Keys.m_res.ToArray(); } }
00110 
00116             public static string[] User { get { return Keys.m_usr.ToArray(); } }
00117 
00121             static Keys()
00122             {
00123                 Keys.m_res.AddRange(Keys.m_sup);
00124                 Keys.m_res.Sort();
00125             }
00126 
00132             public static bool IsValidId(string id)
00133             {
00134                 if (string.IsNullOrEmpty(id)) { return false; }
00135                 if (id.Length != 4) { return false; }
00136                 for (int i = 0; i < 4; i++) {
00137                     if (!char.IsLetterOrDigit(id[i])) { return false; }
00138                 }
00139                 return true;
00140             }
00141 
00142             #region Private Data
00143 
00144             private static List<string> m_sup = new List<string>() {
00145                 Keys.AttachedPicture, Keys.Comment, Keys.Album, Keys.Composer, Keys.Genre,
00146                 Keys.CopyrightInfo, Keys.EncodedBy, Keys.TitleOfSong, Keys.OriginalArtist,
00147                 Keys.Artist, Keys.AlbumArtist, Keys.DiscNumber, Keys.Publisher, Keys.Track,
00148                 Keys.Year, Keys.UserDefinedString, Keys.UserDefinedURL
00149             };
00150 
00151             private static List<string> m_res = new List<string>() {
00152                 "AENC", "COMR", "ENCR", "EQUA", "ETCO", "GEOB", "GRID", "IPLS", "LINK",
00153                 "MCDI", "MLLT", "OWNE", "PRIV", "PCNT", "POPM", "POSS", "RBUF", "RVAD",
00154                 "RVRB", "SYLT", "SYTC", "TBPM", "TDAT", "TDLY", "TEXT", "TFLT", "TIME",
00155                 "TIT1", "TIT3", "TKEY", "TLAN", "TLEN", "TMED", "TOAL", "TOFN", "TOLY",
00156                 "TORY", "TOWN", "TPE3", "TPE4", "TRDA", "TRSN", "TRSO", "TSIZ", "TSRC",
00157                 "TSSE", "UFID", "USER", "USLT", "WCOM", "WCOP", "WOAF", "WOAR", "WOAS",
00158                 "WORS", "WPAY", "WPUB"
00159             };
00160 
00161             private static List<string> m_usr = new List<string>() { "XXXX", "YYYY", "ZZZZ" };
00162 
00163             #endregion
00164         }
00165 
00190         public class StatusFlags
00191         {
00195             public enum PreservationFlag : byte
00196             {
00200                 Preserved = 0x00,
00204                 Discarded = 0x01
00205             }
00206             
00213             public PreservationFlag TagAlter { get; set; }
00219             public PreservationFlag FileAlter { get; set; }
00228             public bool ReadOnly { get; set; }
00229 
00233             public bool HasData
00234             {
00235                 get
00236                 {
00237                     return this.TagAlter == PreservationFlag.Discarded ||
00238                            this.FileAlter == PreservationFlag.Discarded ||
00239                            this.ReadOnly;
00240                 }
00241             }
00242 
00243             public StatusFlags(byte status)
00244             {
00245                 this.SetValues(status);
00246             }
00247 
00248             public void Clear()
00249             {
00250                 this.TagAlter = PreservationFlag.Preserved;
00251                 this.FileAlter = PreservationFlag.Preserved;
00252                 this.ReadOnly = false;
00253             }
00254 
00255             public void Set(byte flag)
00256             {
00257                 this.SetValues(flag);
00258             }
00259 
00260             public byte ToByte()
00261             {
00262                 //   654
00263                 // %0abc0000 
00264                 byte r = (byte)0;
00265                 if (this.TagAlter == PreservationFlag.Discarded) { r = Sylloge.Memory.SetBit(r, 6); }
00266                 if (this.FileAlter == PreservationFlag.Discarded) { r = Sylloge.Memory.SetBit(r, 5); }
00267                 if (this.ReadOnly) { r = Sylloge.Memory.SetBit(r, 4); }
00268                 return r;
00269             }
00270 
00271             private void SetValues(byte flag)
00272             {
00273                 this.TagAlter = (Sylloge.Memory.IsBitSet(flag, 6) ? PreservationFlag.Discarded : PreservationFlag.Preserved);
00274                 this.FileAlter= (Sylloge.Memory.IsBitSet(flag, 5) ? PreservationFlag.Discarded : PreservationFlag.Preserved);
00275                 this.ReadOnly = Sylloge.Memory.IsBitSet(flag, 4);
00276             }
00277         }
00278 
00310         public class FormatFlags
00311         {
00312             private byte m_gi;
00313             private byte m_enc;
00314             private int m_dli;
00315 
00325             public byte GroupingIdentity
00326             {
00327                 get { return this.m_gi; }
00328                 set {
00329                     this.m_gi = value;
00330                     this.HasGroup = true;
00331                 }
00332             }
00342             public bool IsCompressed { get; set; }
00355             public byte Encryption
00356             {
00357                 get { return this.m_enc; }
00358                 set {
00359                     this.m_enc = value;
00360                     this.IsEncrypted = true;
00361                 }
00362             }
00374             public bool IsUnsynchronised { get; set; }
00384             public int DataLengthInicator
00385             {
00386                 get { return this.m_dli; }
00387                 set {
00388                     this.m_dli = value;
00389                     this.HasDataLength = true;
00390                 }
00391             }
00392 
00393             public bool HasGroup { get; set; }
00394             public bool IsEncrypted { get; set; }
00395             public bool HasDataLength { get; set; }
00396             
00397 
00401             public bool HasData
00402             {
00403                 get
00404                 {
00405                     return this.HasGroup ||
00406                            this.IsCompressed ||
00407                            this.IsEncrypted ||
00408                            this.IsUnsynchronised ||
00409                            this.HasDataLength;
00410                 }
00411             }
00412 
00413             public FormatFlags(byte flags)
00414             {
00415                 this.SetValues(flags);
00416             }
00417 
00418             public void Clear()
00419             {
00420                 this.HasGroup = false;
00421                 this.IsCompressed = false;
00422                 this.IsEncrypted = false;
00423                 this.IsUnsynchronised = false;
00424                 this.HasDataLength = false;
00425                 this.DataLengthInicator = 0;
00426                 this.GroupingIdentity = 0;
00427                 this.Encryption = 0;
00428             }
00429 
00430             public void Set(byte flag)
00431             {
00432                 this.SetValues(flag);
00433             }
00434 
00435             public byte ToByte()
00436             {
00437                 //                 6  3210
00438                 //     %0abc0000 %0h00kmnp
00439                 //     
00440                 //     where:
00441                 //     h - Grouping identity
00442                 //     k - Compression
00443                 //     m - Encryption
00444                 //     n - Unsynchronisation
00445                 //     p - Data length indicator
00446                 byte r = (byte)0;
00447                 if (this.HasGroup) { r = Sylloge.Memory.SetBit(r, 6); }
00448                 if (this.IsCompressed) { r = Sylloge.Memory.SetBit(r, 3); }
00449                 if (this.IsEncrypted) { r = Sylloge.Memory.SetBit(r, 2); }
00450                 if (this.IsUnsynchronised) { r = Sylloge.Memory.SetBit(r, 1); }
00451                 if (this.HasDataLength) { r = Sylloge.Memory.SetBit(r, 0); }
00452                 return r;
00453             }
00454 
00455             private void SetValues(byte flag)
00456             {
00457                 this.HasGroup = Sylloge.Memory.IsBitSet(flag, 6);
00458                 this.IsCompressed = Sylloge.Memory.IsBitSet(flag, 3);
00459                 this.IsEncrypted = Sylloge.Memory.IsBitSet(flag, 2);
00460                 this.IsUnsynchronised = Sylloge.Memory.IsBitSet(flag, 1);
00461                 this.HasDataLength = Sylloge.Memory.IsBitSet(flag, 0);
00462             }
00463         }
00464 
00465         #endregion
00466 
00467         #region Tag Members
00468 
00478         public string ID { get; protected set; }
00482         public int Length { get { return ((this.Data == null) ? 0 : this.Data.Length); } }
00486         public StatusFlags Status { get; protected set; }
00490         public FormatFlags Format { get; protected set; }
00494         public byte[] Data { get; protected set; }
00495         
00496         #endregion
00497 
00498         #region Public Functions
00499 
00503         public Frame()
00504         {
00505             this.Initialize("ZXXX", (new byte[2]), null);
00506         }
00507 
00512         public Frame(Frame other)
00513         {
00514             this.Initialize(other.ID, (new byte[] { other.Status.ToByte(), other.Format.ToByte() }), other.Data);
00515         }
00516 
00527         public Frame(byte[] data, bool syncSafe)
00528         {
00529             this.Initialize(ref data, syncSafe);
00530         }
00531 
00535         public byte[] FullFrame
00536         {
00537             get
00538             {
00539                 //byte[] data = new byte[this.FullFrameLength];
00540                 List<byte> data = new List<byte>(this.FullFrameLength);
00541                 if (this.ID.Length < 4) { this.ID = this.ID.PadRight(4, 'X'); }
00542                 if (this.ID.Length > 4) { this.ID = this.ID.Substring(0, 4); }
00543                 //System.Buffer.BlockCopy(ID3.Helper.ISO.GetBytes(this.ID.ToUpper()), 0, data, 0, 4);
00544                 data.AddRange(ID3.Helper.ISO.GetBytes(this.ID.ToUpper()));
00545                 //System.Buffer.BlockCopy(this.Size, 0, data, 4, 4);
00546                 data.AddRange(this.Size);
00547                 //data[8] = this.Status.ToByte();
00548                 data.Add(this.Status.ToByte());
00549                 //data[9] = this.Format.ToByte();
00550                 data.Add(this.Format.ToByte());
00551                 if (this.Format.HasGroup) {
00552                     //data[10] = this.Format.GroupingIdentity;
00553                     data.Add(this.Format.GroupingIdentity);
00554                 }
00555                 if (this.Format.IsEncrypted) {
00556                     //data[11] = this.Format.Encryption;
00557                     data.Add(this.Format.Encryption);
00558                 }
00559                 if (this.Format.HasDataLength) {
00560                     int dli = this.Format.DataLengthInicator;
00561                     if (this.Format.IsUnsynchronised) { dli = ID3.Helper.ToSynchsafeInt(this.Format.DataLengthInicator); }
00562                     byte[] blen = new byte[4];
00563                     System.Buffer.BlockCopy(BitConverter.GetBytes(dli), 0, blen, 0, 4);
00564                     data.AddRange(Sylloge.Memory.SwapByteOrder(blen));
00565                 }
00566                 //System.Buffer.BlockCopy(this.Data, 0, data, 10, this.Data.Length);
00567                 // no need to 'sync/compress' here
00568                 data.AddRange(this.Data);
00569                 return data.ToArray();
00570             }
00571         }
00572 
00576         public int FullFrameLength
00577         {
00578             get
00579             {
00580                 int len = this.Length + 10;
00581                 if (this.Format.HasGroup) { ++len; }
00582                 if (this.Format.IsEncrypted) { ++len; }
00583                 if (this.Format.HasDataLength) { len += 4; }
00584                 return len;
00585             }
00586         }
00587 
00593         public virtual byte[] GenerateTag()
00594         {
00595             return this.FullFrame;
00596         }
00597 
00601         public byte[] Size
00602         {
00603             get
00604             {
00605                 int len = ID3.Helper.ToSynchsafeInt((int)this.Length);
00606                 byte[] blen = new byte[4];
00607                 System.Buffer.BlockCopy(BitConverter.GetBytes(len), 0, blen, 0, 4);
00608                 return Memory.SwapByteOrder(blen);
00609             }
00610         }
00611 
00616         public override string ToString()
00617         {
00618             return this.ID;
00619         }
00620 
00621         #endregion
00622 
00623         #region Protected Functions
00624         // these functions are specific to derived classes
00625 
00631         protected Frame(string id)
00632         {
00633             this.Initialize(id, (new byte[2]), null);
00634         }
00635 
00645         protected Frame(string id, byte[] data)
00646         {
00647             this.Initialize(id, (new byte[2]), data);
00648         }
00649 
00660         protected Frame(string id, byte[] flags, byte[] data)
00661         {
00662             this.Initialize(id, flags, data);
00663         }
00664 
00676         protected Frame(string id, byte status, byte format, byte[] data)
00677         {
00678             this.Initialize(id, (new byte[] { status, format }), data);
00679         }
00680 
00681         #endregion
00682 
00683         #region Private Functions
00684 
00689         private void Initialize(ref byte[] data, bool syncSafe)
00690         {
00691             this.ID = ID3.Helper.ISO.GetString(data, 0, 4).ToUpper();
00692             this.Status = new StatusFlags(data[8]);
00693             this.Format = new FormatFlags(data[9]);
00694             // length is big-endian, need to swap bytes then can get proper length
00695             byte[] ts = new byte[4];
00696             System.Buffer.BlockCopy(data, 4, ts, 0, 4);
00697             int len = BitConverter.ToInt32(Sylloge.Memory.SwapByteOrder(ts, 0, 4), 0);
00698             if (syncSafe) { len = ID3.Helper.FromSynchsafeInt(len); }
00699             this.Data = new byte[len];
00700             System.Buffer.BlockCopy(data, 10, this.Data, 0, len);
00701         }
00702 
00703         private void Initialize(string id, byte[] flags, byte[] data)
00704         {
00705             this.ID = id;
00706             if (flags != null && flags.Length > 1) {
00707                 this.Status = new StatusFlags(flags[0]);
00708                 this.Format = new FormatFlags(flags[1]);
00709             }
00710             if (data != null) { this.Data = data; }
00711         }
00712 
00713         #endregion
00714 
00715         #region Static Functions
00716 
00723         public static byte[] GetFullTagData(string id, byte[] data)
00724         {
00725             return (new Frame(id, data)).FullFrame;
00726         }
00727         
00738         public static ID3.Frame Parse(byte[] data, bool unsync)
00739         {
00740             if (data == null) { return null; }
00741             if (data.Length < 10) { return null; }
00742             // ID3 frame headers follow a specific format:
00743             // Frame ID: xx xx xx xx (four characters) 
00744             string tag_id = ID3.Helper.ISO.GetString(data, 0, 4).ToUpper();
00745             if (!Frame.Keys.IsValidId(tag_id)) { return null; }
00746             List<byte> bdata = new List<byte>(data);
00747             bdata.RemoveRange(0, 4); // remove tag_id
00748             // Frame Size:   xx xx xx xx (this size is the total length - the header)
00749             int sz = BitConverter.ToInt32(Sylloge.Memory.SwapByteOrder(bdata.ToArray(), 0, 4), 0);
00750             if (unsync) { sz = ID3.Helper.FromSynchsafeInt(sz); }
00751             bdata.RemoveRange(0, 4); // remove size bytes
00752             // Flags:  xx xx
00753             Frame ret = new Frame(tag_id);
00754             ret.Status = new StatusFlags(bdata[0]);
00755             ret.Format = new FormatFlags(bdata[1]);
00756             bdata.RemoveRange(0, 2);
00757             if (ret.Format.HasData) {
00758                 if (ret.Format.HasGroup) {
00759                     ret.Format.GroupingIdentity = bdata[0];
00760                     bdata.RemoveAt(0);
00761                 }
00762                 if (ret.Format.IsEncrypted) {
00763                     ret.Format.Encryption = bdata[0];
00764                     bdata.RemoveAt(0);
00765                 }
00766                 if (ret.Format.HasDataLength) {
00767                     int dli = BitConverter.ToInt32(Sylloge.Memory.SwapByteOrder(bdata.ToArray(), 0, 4), 0);
00768                     if (ret.Format.IsUnsynchronised) { dli = ID3.Helper.FromSynchsafeInt(dli); }
00769                     bdata.RemoveRange(0, 4);
00770                     ret.Format.DataLengthInicator = dli;
00771                 }
00772             }
00773             if (sz > bdata.Count) { sz = bdata.Count; }
00774             ret.Data = new byte[sz];
00775             // copy all of the data, the individual tag is responsible
00776             // for 'unsyncing' the data (if applicable)
00777             System.Buffer.BlockCopy(bdata.ToArray(), 0, ret.Data, 0, sz);
00778             //bdata.RemoveRange(0, sz); // strictly speaking, not nessecary
00779             return ret;
00780         }
00781 
00788         public static byte[] Encrypt(byte[] data, byte method)
00789         {
00790             return data;
00791         }
00792 
00799         public static byte[] Decrypt(byte[] data, byte method)
00800         {
00801             return data;
00802         }
00803 
00809         public static byte[] Compress(byte[] data)
00810         {
00811             return data;
00812         }
00813 
00819         public static byte[] Decompress(byte[] data)
00820         {
00821             return data;
00822         }
00823 
00829         public static byte[] SyncData(byte[] data)
00830         {
00831             List<byte> ret = new List<byte>(data);
00832             for (int i = 0; i < ret.Count; i++) {
00833                 if ((i + 1) > ret.Count) { break; }
00834                 if (ret[i] == 0xFF && ret[i + 1] != 0x00) {
00835                     ret.Insert((i + 1), 0x00);
00836                 }
00837             }
00838             /*
00839             Whenever a false synchronisation is found within the tag, one zeroed
00840             byte is inserted after the first false synchronisation byte. The
00841             format of synchronisations that should be altered by ID3 encoders is
00842             as follows:
00843                 %11111111 111xxxxx
00844             and should be replaced with:
00845                 %11111111 00000000 111xxxxx
00846             This has the side effect that all $FF 00 combinations have to be
00847             altered, so they will not be affected by the decoding process.
00848             Therefore all the $FF 00 combinations have to be replaced with the
00849             $FF 00 00 combination during the unsynchronisation.
00850             To indicate usage of the unsynchronisation, the unsynchronisation
00851             flag in the frame header should be set.
00852              */
00853             return ret.ToArray();
00854         }
00855 
00862         public static byte[] UnsyncData(byte[] data, int dli)
00863         {
00864             int max = (dli > 0 ? dli : data.Length);
00865             List<byte> ret = new List<byte>(data);
00866             for (int i = 0; i < max; ++i) {
00867                 if ((i + 1) > ret.Count) { break; }
00868                 if (ret[i] == 0xFF && ret[i + 1] == 0x00) {
00869                     ret.RemoveAt(i + 1);
00870                 }
00871             }
00872             /*
00873             Whenever a false synchronisation is found within the tag, one zeroed
00874             byte is inserted after the first false synchronisation byte. The
00875             format of synchronisations that should be altered by ID3 encoders is
00876             as follows:
00877                 %11111111 111xxxxx
00878             and should be replaced with:
00879                 %11111111 00000000 111xxxxx
00880             This has the side effect that all $FF 00 combinations have to be
00881             altered, so they will not be affected by the decoding process.
00882             Therefore all the $FF 00 combinations have to be replaced with the
00883             $FF 00 00 combination during the unsynchronisation.
00884             To indicate usage of the unsynchronisation, the unsynchronisation
00885             flag in the frame header should be set.
00886              */
00887             return ret.ToArray();
00888         }
00889 
00890         #endregion
00891     }
00892 }
 All Classes Namespaces Files Functions Variables Enumerations Properties Events