![]() |
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 V1 00012 { 00013 #region dev notes 00014 00015 /* Tag info 00016 ID3v1 Tag information 00017 Last 128 bytes of MP3 file 00018 first 3 bytes MUST be 'TAG' for it to be valid 00019 Length(bytes) Position(bytes) Description 00020 3 (0-2) Tag identification. Must contain 'TAG' if tag exists and is correct. 00021 30 (3-32) Title 00022 30 (33-62) Artist 00023 30 (63-92) Album 00024 4 (93-96) Year 00025 30 (97-126) Comment 00026 1 (127) Genre 00027 00028 DEV_NOTE: The 'TAG' portion of the ID3v1 tag is read in ANSI. 00029 */ 00030 00031 #endregion 00032 00033 #region Extended V1 (TAG+) Header Class 00034 00035 public class TagPlus 00036 { 00037 public enum SpeedDesignator 00038 { 00039 Unset = 0, 00040 Slow, 00041 Medium, 00042 Fast, 00043 Hardcore 00044 } 00045 00049 public string ExtendedTitle { get; set; } 00053 public string ExtendedArtist { get; set; } 00057 public string ExtendedAlbum { get; set; } 00061 public SpeedDesignator Speed { get; set; } 00065 public string Genre { get; set; } 00069 public string StartTime { get; set; } 00073 public string EndTime { get; set; } 00078 public byte[] Data { get; protected set; } 00082 public bool IsValid { get; protected set; } 00083 00084 public TagPlus() 00085 { 00086 this.Clear(); 00087 } 00088 00089 public void Clear() 00090 { 00091 this.ExtendedTitle = string.Empty; 00092 this.ExtendedArtist = string.Empty; 00093 this.ExtendedAlbum = string.Empty; 00094 this.Speed = SpeedDesignator.Unset; 00095 this.Genre = string.Empty; 00096 this.StartTime = string.Empty; 00097 this.EndTime = string.Empty; 00098 } 00099 00100 public static TagPlus Parse(byte[] b) 00101 { 00102 if (b == null) { throw new System.ArgumentNullException("byte value cannot be null"); } 00103 if (b.Length != 227) { throw new System.ArgumentOutOfRangeException("ID3v1 extende tag length must be 227 bytes"); } 00104 System.Text.Encoding enc = ID3.Helper.ISO; 00105 if (enc.GetString(b, 0, 4) != "TAG+") { throw new System.ArgumentException("ID3v1 extended tag must begin with 'TAG+'"); }; 00106 try { 00107 char[] tc = new char[] { '\0', ' ' }; 00108 TagPlus ret = new TagPlus(); 00109 /* 00110 Note: The extended tag is 227 bytes long, and placed before the ID3v1 tag. 00111 Field Length Description 00112 header 4 "TAG+" 00113 title 60 60 characters of the title 00114 artist 60 60 characters of the artist name 00115 album 60 60 characters of the album name 00116 speed 1 0=unset, 1=slow, 2= medium, 3=fast, 4=hardcore 00117 genre 30 A free-text field for the genre 00118 start-time 6 the start of the music as mmm:ss 00119 end-time 6 the end of the music as mmm:ss 00120 */ 00121 ret.ExtendedTitle = enc.GetString(b, 4, 60).TrimEnd(tc); // 4+60 00122 ret.ExtendedArtist = enc.GetString(b, 64, 60).TrimEnd(tc); // 64+60 00123 ret.ExtendedAlbum = enc.GetString(b, 124, 60).TrimEnd(tc); // 124+60 00124 ret.Speed = (SpeedDesignator)b[184]; // 184+1 00125 ret.Genre = enc.GetString(b, 185, 30).TrimEnd(tc); // 185+30 00126 ret.StartTime = enc.GetString(b, 215, 6).TrimEnd(tc); // 215+6 00127 ret.EndTime = enc.GetString(b, 221, 6).TrimEnd(tc); // 221+6 00128 ret.Data = b; 00129 ret.IsValid = true; 00130 return ret; 00131 } catch (Exception ex) { 00132 throw ex; 00133 } 00134 } 00135 } 00136 00137 #endregion 00138 00139 #region ID3 Tag Values 00140 00144 public string Artist { get; set; } 00148 public string Album { get; set; } 00152 public string Title { get; set; } 00156 public string Comment { get; set; } 00160 public string File { get; protected set; } 00164 public ID3.Genre Genre { get; set; } 00168 public bool IsValid { get; protected set; } 00172 public int Track { get; set; } 00176 public string Year { get; protected set; } 00180 public System.Version Version { get; protected set; } 00185 public byte[] Data { get; protected set; } 00189 public TagPlus Extended { get; protected set; } 00190 00191 #endregion 00192 00193 #region Methods 00194 00198 public V1() 00199 { 00200 this.Clear(); 00201 this.Version = new Version(1, 0, 0); 00202 } 00203 00208 public void Clear() 00209 { 00210 this.IsValid = false; 00211 this.Title = string.Empty; 00212 this.Artist = string.Empty; 00213 this.Album = string.Empty; 00214 this.Year = "1970"; 00215 this.Comment = string.Empty; 00216 this.Genre = ID3.Genre.Other; 00217 this.Track = 0; 00218 this.Extended = new TagPlus(); 00219 } 00220 00225 public bool Save() 00226 { 00227 if (!System.IO.File.Exists(this.File)) { return false; } 00228 try { 00229 /* 00230 * Last 128 bytes of MP3 file 00231 first 3 bytes MUST be 'TAG' for it to be valid 00232 Length(bytes) Position(bytes) Description 00233 3 (0-2) Tag identification. Must contain 'TAG' if tag exists and is correct. 00234 30 (3-32) Title 00235 30 (33-62) Artist 00236 30 (63-92) Album 00237 4 (93-96) Year 00238 30* (97-126)* Comment 00239 1* (125)* 0-Byte pad for track (if present) 00240 1* (126)* Track 00241 1 (127) Genre 00242 00243 Note that per ID3v1.1, if byte 125 is a binary 0, then byte 126 is the 00244 track number. 00245 */ 00246 System.Text.Encoding enc = ID3.Helper.ISO; 00247 byte[] tdata = new byte[128]; 00248 Sylloge.Memory.Copy(enc.GetBytes("TAG"), 0, tdata, 0, 3); 00249 Sylloge.Memory.Copy(enc.GetBytes(this.Title), 0, tdata, 3, 30); 00250 Sylloge.Memory.Copy(enc.GetBytes(this.Artist), 0, tdata, 33, 30); 00251 Sylloge.Memory.Copy(enc.GetBytes(this.Album), 0, tdata, 63, 30); 00252 Sylloge.Memory.Copy(enc.GetBytes(this.Year), 0, tdata, 93, 4); 00253 if (this.Track > 0) { 00254 Sylloge.Memory.Copy(enc.GetBytes(this.Comment), 0, tdata, 97, 28); 00255 tdata[125] = 0x00; 00256 tdata[126] = (byte)this.Track; 00257 } else { 00258 Sylloge.Memory.Copy(enc.GetBytes(this.Comment), 0, tdata, 97, 30); 00259 } 00260 tdata[127] = (byte)this.Genre; 00261 try { 00262 byte[] dval = new byte[128]; 00263 System.IO.FileStream fin = new System.IO.FileStream(this.File, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite); 00264 fin.Seek(-128, System.IO.SeekOrigin.End); 00265 fin.Read(dval, 0, 128); 00266 if (enc.GetString(dval, 0, 3) == "TAG") { 00267 // There is a valid tag so we need to overwrite it 00268 fin.Seek(-128, System.IO.SeekOrigin.End); 00269 fin.SetLength(fin.Length - 128); 00270 } 00271 fin.Seek(0, System.IO.SeekOrigin.End); // Go to the end of the file 00272 fin.Write(tdata, 0, 128); 00273 fin.Close(); 00274 this.Data = tdata; 00275 return true; 00276 } catch (Exception ex) { 00277 throw ex; 00278 } 00279 } catch (Exception ex) { 00280 throw ex; 00281 } 00282 } 00283 00287 public void SetFile(string file) { this.File = file; } 00288 00292 public void SetYear(DateTime d) 00293 { 00294 this.Year = d.ToString("yyyy"); 00295 } 00296 00300 public void SetYear(string d) 00301 { 00302 if (!string.IsNullOrEmpty(d)) { 00303 if (!Sylloge.Extensions.TypeExtensions.IsNumeric(d)) { throw new System.ArgumentException("Year must be a numeric value"); } 00304 if (d.Length > 4) { throw new System.ArgumentOutOfRangeException("Year cannot be more than 4 digits"); } 00305 } 00306 this.Year = d; 00307 } 00308 00312 public void SetVersion(System.Version ver) { this.Version = ver; } 00313 00317 public void SetVersion(string ver) 00318 { 00319 this.Version = System.Version.Parse(ver); 00320 } 00321 00326 public override string ToString() 00327 { 00328 return this.Artist + " - " + this.Title; 00329 } 00330 00331 #endregion 00332 00333 #region Static Methods 00334 00340 public static V1 Parse(byte[] b) 00341 { 00342 if (b == null) { throw new System.ArgumentNullException("byte value cannot be null"); } 00343 if (b.Length != 128) { throw new System.ArgumentOutOfRangeException("ID3v1 byte tag length must be 128 bytes"); } 00344 System.Text.Encoding enc = ID3.Helper.ISO; 00345 if (enc.GetString(b, 0, 3) != "TAG") { throw new System.ArgumentException("ID3v1 tag must begin with 'TAG'"); }; 00346 try { 00347 char[] tc = new char[] { '\0', ' ' }; 00348 V1 ret = new V1(); 00349 /* 00350 * Last 128 bytes of MP3 file 00351 first 3 bytes MUST be 'TAG' for it to be valid 00352 Length(bytes) Position(bytes) Description 00353 3 (0-2) Tag identification. Must contain 'TAG' if tag exists and is correct. 00354 30 (3-32) Title 00355 30 (33-62) Artist 00356 30 (63-92) Album 00357 4 (93-96) Year 00358 30* (97-126)* Comment 00359 1* (125)* 0-Byte pad for track (if present) 00360 1* (126)* Track 00361 1 (127) Genre 00362 00363 Note that per ID3v1.1, if byte 125 is a binary 0, then byte 126 is the 00364 track number. 00365 */ 00366 ret.Title = enc.GetString(b, 3, 30).TrimEnd(tc); // 3-32 00367 ret.Artist = enc.GetString(b, 33, 30).TrimEnd(tc); // 33-62 00368 ret.Album = enc.GetString(b, 63, 30).TrimEnd(tc); // 63-92 00369 ret.Year = enc.GetString(b, 93, 4).TrimEnd(tc); ; // 93-96 00370 if (b[125] == 0x00) { // 97-124 (track present) 00371 // doesn't have a full comment, so set track accordingly (if set) 00372 ret.Comment = enc.GetString(b, 97, 28).TrimEnd(tc); // 28 00373 ret.Track = (int)b[126]; 00374 } else { // 97-126 (no track present) 00375 ret.Comment = enc.GetString(b, 97, 30).TrimEnd(tc); 00376 ret.Track = 0; 00377 } 00378 ret.Genre = (ID3.Genre)b[127]; 00379 ret.Data = b; 00380 ret.IsValid = true; 00381 return ret; 00382 } catch (Exception ex) { 00383 throw ex; 00384 } 00385 } 00386 00392 public static V1 Parse(string file) 00393 { 00394 // no try catch here since we want the exception to propigate up 00395 if (System.IO.File.Exists(file)) { 00396 byte[] tag = new byte[128]; 00397 byte[] tagp = new byte[227]; 00398 System.IO.FileStream fin = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite); 00399 fin.Seek(-355, System.IO.SeekOrigin.End); 00400 fin.Read(tagp, 0, 227); // read the "TAG+" first (doesn't matter if it exists since this advances the read stream) 00401 fin.Read(tag, 0, 128); // read the "TAG" next 00402 fin.Close(); fin = null; 00403 if (ID3.Helper.ISO.GetString(tag, 0, 3) == "TAG") { 00404 // Its a valid tag, so continue 00405 V1 ret = V1.Parse(tag); 00406 ret.File = file; 00407 if (ID3.Helper.ISO.GetString(tagp, 0, 4) == "TAG+") { 00408 ret.Extended = TagPlus.Parse(tagp); 00409 } 00410 return ret; 00411 } 00412 } 00413 return null; 00414 } 00415 00422 public static bool TryParse(byte[] b, out V1 v1) 00423 { 00424 try { 00425 v1 = V1.Parse(b); 00426 return true; 00427 } catch (Exception) { 00428 v1 = null; 00429 } 00430 return false; 00431 } 00432 00439 public static bool TryParse(string file, out V1 v1) 00440 { 00441 try { 00442 v1 = V1.Parse(file); 00443 return true; 00444 } catch (Exception) { 00445 v1 = null; 00446 } 00447 return false; 00448 } 00449 00461 public static bool Save(string fileName, string title, string artist, string album, string year, string comment, int track, ID3.Genre genre) 00462 { 00463 V1 tag = new V1(); 00464 tag.File = fileName; 00465 tag.IsValid = true; 00466 tag.Title = title; 00467 tag.Artist = artist; 00468 tag.Album = album; 00469 tag.Year = year; 00470 tag.Comment = comment; 00471 tag.Track = track; 00472 tag.Genre = genre; 00473 return tag.Save(); 00474 } 00475 00476 #endregion 00477 } 00478 }
1.7.4