Sylloge
A C# helper library
code/Audio/Metadata/APE/V2.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.APE
00007 {
00011     public class V2
00012     {
00016         public class Tag
00017         {
00021             public enum ValueType
00022             {
00026                 UTF8Text,
00030                 Binary,
00034                 ExternalStore,
00038                 Reserved
00039             }
00040 
00044             public int Length { get; protected set; }
00048             public string Key { get; protected set; }
00052             public byte[] Flags { get; protected set; }
00056             public byte[] Data { get; protected set; }
00060             public bool HasHeader { get; protected set; }
00064             public bool HasFooter { get; protected set; }
00068             public bool IsFooter { get; protected set; }
00072             public ValueType DataType { get; protected set; }
00076             public bool ReadOnly { get; protected set; }
00077 
00081             public int TotalLength
00082             {
00083                 get
00084                 {
00085                     return this.Length + this.Key.Length + 1 + 8;
00086                 }
00087             }
00088 
00089             public Tag()
00090             {
00091             }
00092 
00098             public static Tag Parse(byte[] data)
00099             {
00100                 Tag ret = null;
00101                 try {
00102                     ret = new Tag();
00103                     ret.Length = BitConverter.ToInt32(data, 0);
00104                     ret.Flags = new byte[4] { data[4], data[5], data[6], data[7] };
00105                     // Bit 31   0: Tag contains no header, 1: Tag contains a header
00106                     ret.HasHeader = Sylloge.Memory.IsBitSet(data[4], 7);
00107                     // Bit 30   0: Tag contains a footer, 1: Tag contains no footer
00108                     ret.HasFooter = Sylloge.Memory.IsBitSet(data[4], 6);
00109                     // Bit 29   0: This is the footer, not the header, 1: This is the header, not the footer
00110                     ret.IsFooter = Sylloge.Memory.IsBitSet(data[4], 5);
00111                     // Bit 28...3       Undefined, must be zero
00112                     /*
00113                     * Bit 2...1     0: Item contains text information coded in UTF-8
00114                     *               1: Item contains binary information*
00115                     *               2: Item is a locator of external stored information**
00116                     *               3: reserved
00117                     */
00118                     ret.DataType = ValueType.UTF8Text;
00119                     bool b1 = Sylloge.Memory.IsBitSet(data[7], 1);
00120                     bool b2 = Sylloge.Memory.IsBitSet(data[7], 2);
00121                     if (b1 || b2) {
00122                         if (b1 && b2) {
00123                             ret.DataType = ValueType.Reserved;
00124                         } else {
00125                             if (b1) {
00126                                 ret.DataType = ValueType.Binary;
00127                             } else {
00128                                 ret.DataType = ValueType.ExternalStore;
00129                             }
00130                         }
00131                     }
00132                     // Bit 0    0: Tag or Item is Read/Write, 1: Tag or Item is Read Only 
00133                     ret.ReadOnly = Sylloge.Memory.IsBitSet(data[7], 0);
00134                     int i = 8; List<byte> kd = new List<byte>();
00135                     for (; i < data.Length; i++) {
00136                         if (data[i] == 0x00) { break; }
00137                         kd.Add(data[i]);
00138                     }
00139                     ret.Key = System.Text.Encoding.ASCII.GetString(kd.ToArray());
00140                     ret.Data = new byte[ret.Length];
00141                     System.Buffer.BlockCopy(data, (i+1), ret.Data, 0, ret.Length);
00142                     /*
00143                     Size of the Item Value, Bits 0...7
00144                     Size of the Item Value, Bits 8...15
00145                     Size of the Item Value, Bits 16...23
00146                     Size of the Item Value, Bits 24...31
00147                         32 bits         Length len of the assigned value in bytes
00148 
00149                     Item Flags, Bits 0...7
00150                     Item Flags, Bits 8...15
00151                     Item Flags, Bits 16...23
00152                     Item Flags, Bits 24...31
00153                         32 bits         Item flags
00154                     Item Key    m bytes         Item key, can contain ASCII characters from 0x20 (Space) up to 0x7E (Tilde)
00155                     0x00        1byte   Item key terminator
00156                     Item Value  len bytes       Item value, can be binary data or UTF-8 string 
00157                      */
00158                 } catch (Exception) {
00159                     ret = null;
00160                 }
00161                 return ret;
00162             }
00163         }
00164 
00168         public System.Version Version { get; protected set; }
00172         public bool IsValid { get; protected set; }
00176         public int Size { get; protected set; }
00180         public int TagCount { get; protected set; }
00184         public byte[] GlobalFlags { get; protected set; }
00188         public byte[] Data { get; protected set; }
00192         public List<Tag> Tags { get; protected set; }
00193 
00197         public V2()
00198         {
00199             this.Clear();
00200         }
00201 
00205         public void Clear()
00206         {
00207             if (this.Tags != null && this.Tags.Count > 0) { this.Tags.Clear(); this.Tags.TrimExcess(); }
00208             this.Tags = new List<Tag>();
00209             this.Version = new Version(2, 0, 0, 0);
00210             this.Size = 0;
00211             this.TagCount = 0;
00212             this.GlobalFlags = null;
00213             this.Data = null;
00214             this.IsValid = false;
00215         }
00216 
00222         public static V2 Parse(byte[] b)
00223         {
00224             if (b == null) { throw new System.ArgumentNullException("byte value cannot be null"); }
00225             System.Text.Encoding enc = System.Text.Encoding.UTF8;
00226             if (System.Text.Encoding.ASCII.GetString(b, 0, 8) != "APETAGEX") { throw new System.ArgumentException("No valid tag detected"); };
00227             try {
00228                 char[] tc = new char[] { '\0', ' ' };
00229                 V2 ret = V2.ParseHeader(b);
00230                 ret.Data = b;
00231                 ret.IsValid = true;
00232                 List<byte> data = new List<byte>(b);
00233                 data.RemoveRange(0, 32); // get rid of the header
00234                 while (data.Count > 0) {
00235                     Tag t = Tag.Parse(data.ToArray());
00236                     if (t != null) {
00237                         data.RemoveRange(0, t.TotalLength);
00238                         ret.Tags.Add(t);
00239                     } else {
00240                         break;
00241                     }
00242                 }
00243                 return ret;
00244             } catch (Exception ex) {
00245                 throw ex;
00246             }
00247         }
00248 
00254         private static V2 ParseHeader(byte[] b)
00255         {
00256             if (System.Text.Encoding.ASCII.GetString(b, 0, 8) == "APETAGEX") {
00257                 V2 ret = new V2();
00258                 List<byte> data = new List<byte>(b);
00259                 //Preamble      64 bits         { 'A', 'P', 'E', 'T', 'A', 'G', 'E', 'X' }
00260                 data.RemoveRange(0, 8);
00261                 // Version 32 bits  1000 = Version 1.000 (old), 2000 = Version 2.000 (new)
00262                 // Version Number, Bits 0...7
00263                 // Version Number, Bits 8...15
00264                 // Version Number, Bits 16...23
00265                 // Version Number, Bits 24...31
00266                 ret.Version = new Version(data[0], data[1], data[2], data[3]);
00267                 data.RemoveRange(0, 4);
00268                 // Size 32 bits         Tag size in bytes including footer and all tag items excluding the header to be as compatible as possible with APE Tags 1.000
00269                 // Tag Size, Bits 0... 7
00270                 // Tag Size, Bits 8...15
00271                 // Tag Size, Bits 16...23
00272                 // Tag Size, Bits 24...31
00273                 ret.Size = BitConverter.ToInt32(data.ToArray(), 0);
00274                 ret.Data = new byte[ret.Size];
00275                 data.RemoveRange(0, 4);
00276                 // Tag Count    32 bits         Number of items in the Tag (n)
00277                 // Item Count, Bits 0... 7
00278                 // Item Count, Bits 8...15
00279                 // Item Count, Bits 16...23
00280                 // Item Count, Bits 24...31
00281                 ret.TagCount = BitConverter.ToInt32(data.ToArray(), 0);
00282                 data.RemoveRange(0, 4);
00283                 // Tag flags    32 bits Global flags of all items (there are also private flags for every item)
00284                 // Tags Flags, Bits 0... 7
00285                 // Tags Flags, Bits 8...15
00286                 // Tags Flags, Bits 16...23
00287                 // Tags Flags, Bits 24...31
00288                 ret.GlobalFlags = new byte[4] { data[0], data[1], data[2], data[3] };
00289                 data.RemoveRange(0, 4);
00290                 // Reserved     64 bits         Must be zero
00291                 long v = BitConverter.ToInt64(data.ToArray(), 0);
00292                 if (v == 0) { return ret; }
00293             }
00294             return null;
00295         }
00296 
00302         public static V2 Parse(string file)
00303         {
00304             // no try catch here since we want the exception to propigate up
00305             if (System.IO.File.Exists(file)) {
00306                 byte[] tag = new byte[128];
00307                 byte[] tagp = new byte[227];
00308                 System.IO.FileStream fin = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);
00309                 long strt = -32; // 32 bytes for an APE header starting with "APETAGEX" (remember 32bits = 4bytes)
00310                 fin.Seek(-355, System.IO.SeekOrigin.End);
00311                 fin.Read(tagp, 0, 227); // read the "TAG+" first (doesn't matter if it exists since this advances the read stream)
00312                 fin.Read(tag, 0, 128); // read the "TAG" next
00313                 if (ID3.Helper.ISO.GetString(tag, 0, 3) == "TAG") { strt -= 128; }
00314                 if (ID3.Helper.ISO.GetString(tagp, 0, 4) == "TAG+") { strt -= 227; }
00315                 tag = new byte[32];
00316                 fin.Seek(strt, System.IO.SeekOrigin.End);
00317                 fin.Read(tag, 0, 32);
00318                 V2 ret = V2.ParseHeader(tag);
00319                 if (ret != null) {
00320                     strt = -(ret.Size + Math.Abs(strt));
00321                     fin.Seek(strt, System.IO.SeekOrigin.End);
00322                     fin.Read(ret.Data, 0, ret.Size);
00323                     ret.IsValid = true;
00324                     ret = V2.Parse(ret.Data);
00325                 }
00326                 fin.Close(); fin = null;
00327                 return ret;
00328             }
00329             return null;
00330         }
00331     }
00332 }
 All Classes Namespaces Files Functions Variables Enumerations Properties Events