位於 MP3 檔案**最後 128 位元組**,固定長度,結構如下:
| 欄位 | 長度 | 位移 | 說明 |
|---|---|---|---|
| Tag 標識 | 3 | 0-2 | 必為 "TAG" |
| Title | 30 | 3-32 | 曲目標題 |
| Artist | 30 | 33-62 | 歌手名稱 |
| Album | 30 | 63-92 | 專輯名稱 |
| Year | 4 | 93-96 | 發行年份 |
| Comment | 30 | 97-126 | 備註(v1.1 最後 1 位元組為 Track Number) |
| Genre | 1 | 127 | 流派代碼 (0-79, 255=Unknown) |
欄位不足以空白 (0x20) 或 null (0x00) 填充。WinAmp 習慣用空白填充。
0: Blues 1: Classic Rock 2: Country 3: Dance 4: Disco
5: Funk 6: Grunge 7: Hip-Hop 8: Jazz 9: Metal
10: New Age 11: Oldies 12: Other 13: Pop 14: R&B
15: Rap 16: Reggae 17: Rock 18: Techno 19: Industrial
20: Alternative 21: Ska 22: Death Metal 23: Pranks 24: Soundtrack
... 79: Hard Rock, 255: Unknown
using System;
using System.IO;
using System.Text;
public class Mp3TagReader {
public string Title { get; private set; }
public string Artist { get; private set; }
public string Album { get; private set; }
public string Year { get; private set; }
public string Comment { get; private set; }
public string Genre { get; private set; }
public byte TrackNumber { get; private set; } // ID3v1.1
public bool HasTag { get; private set; }
private static readonly string[] Genres = new[] {
"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
"Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B",
"Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska",
"Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop",
"Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental",
"Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "Alt. Rock",
"Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop",
"Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial",
"Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy",
"Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle",
"Native American", "Cabaret", "New Wave", "Psychedelic", "Rave",
"Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz",
"Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock"
};
public Mp3TagReader(string filePath) {
Parse(filePath);
}
public void Parse(string filePath) {
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) {
if (fs.Length < 128) { HasTag = false; return; }
fs.Seek(-128, SeekOrigin.End);
byte[] tag = new byte[128];
fs.Read(tag, 0, 128);
// 驗證 TAG 標識
string tagId = Encoding.Default.GetString(tag, 0, 3);
if (tagId != "TAG") { HasTag = false; return; }
HasTag = true;
Title = Clean(Encoding.Default.GetString(tag, 3, 30));
Artist = Clean(Encoding.Default.GetString(tag, 33, 30));
Album = Clean(Encoding.Default.GetString(tag, 63, 30));
Year = Clean(Encoding.Default.GetString(tag, 93, 4));
Comment = Clean(Encoding.Default.GetString(tag, 97, 30));
Genre = Genres.Length > tag[127] ? Genres[tag[127]] : "Unknown";
// ID3v1.1: Comment 最後一位若為 0,倒數第二位為 Track Number
if (tag[125] == 0 && tag[126] != 0) {
TrackNumber = tag[126];
Comment = Clean(Encoding.Default.GetString(tag, 97, 28)); // 前 28 位
}
}
}
private string Clean(string s) {
return s.Trim('\0', ' '); // 移除前後空白與 null
}
}
var tag = new Mp3TagReader(@"C:\Music\song.mp3");
if (tag.HasTag) {
Console.WriteLine($"標題: {tag.Title}");
Console.WriteLine($"歌手: {tag.Artist}");
Console.WriteLine($"專輯: {tag.Album}");
Console.WriteLine($"年份: {tag.Year}");
Console.WriteLine($"流派: {tag.Genre}");
Console.WriteLine($"軌道: {tag.TrackNumber}");
Console.WriteLine($"備註: {tag.Comment}");
} else {
Console.WriteLine("無 ID3v1 TAG");
}
ID3v1 侷限極大(長度固定、無 Unicode、無封面)。實務建議直接使用成熟庫:
using TagLib;
var f = TagLib.File.Create("song.mp3");
Console.WriteLine(f.Tag.Title);
Console.WriteLine(f.Tag.FirstAlbumArtist);
Console.WriteLine(f.Tag.Year);
Console.WriteLine(f.Tag.Genres.FirstOrDefault());
// 寫入標籤
f.Tag.Title = "新標題";
f.Tag.Artists = new[] { "新歌手" };
f.Tag.Pictures = new[] { new TagLib.Picture(new TagLib.ByteVector(File.ReadAllBytes("cover.jpg"))) };
f.Save();
ID3v1 解析邏輯簡單,適合教學或極簡場景。實際專案**強烈建議使用 TagLib Sharp**,一次搞定所有格式、編碼、封面、版本相容問題,避免重複造輪子。