![]() |
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.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 }
1.7.4