![]() |
Sylloge
A C# helper library
|
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 }
1.7.4