背景
前几天了解到MP3文件的ID3v1信息和ID3v2信息结构,其中ID3v1信息存储的内容比较简单,有歌曲名、艺术家、专辑、发行年、备注、曲目编号、流派。其版本有1.0和1.1,其中1.0没有曲目编号。
正文
ID3v1信息存储在MP3文件的尾部,一共128字节,可有可无。以下是其信息排列:
ID3v1.1(1.0中无曲目编号,所以其备注包括Null character和Track,共30字节)
Tag Field | Data(character) | Offset(from end of mp3) |
TAG | 3 | -128 to -126 |
Song title(歌曲名) | 30 | -125 to -96 |
Artist(艺术家) | 30 | -95 to -66 |
Album(专辑) | 30 | -65 to -36 |
Year(发行年) | 4 | -35 to -32 |
Comment(备注) | 28 | -31 to -4 |
Null character | 1 | -3 |
Track(曲目编号) | 1 | -2 |
Genre(流派) | 1 | -1 |
说明
Year:以字符串形式存在,获取时无需将其从byte[4]转换成int。
Null character:此为保留位,1.1中始终为(byte)0,所以通过其判断Comment的大小和ID3v1的版本。
Track: int类型,其值为 0 - 255,毕竟1byte只能存这么多。
Genre: int类型,其对应类型详见附录。
知道了以上信息就可以开始编码了。
public
enum
ID3v1TagVersion
{
ID3v10,
ID3v11
}
{
ID3v10,
ID3v11
}
ID3v1
private
ID3v1(){ }
public ID3v1( string path)
{
MP3Path = path;
ReadPath(MP3Path);
}
public ID3v1( string path)
{
MP3Path = path;
ReadPath(MP3Path);
}
私有字段和共属性
#region
Private Fields
private ID3v1TagVersion _tagVersion;
private string _title; // 30 characters
private string _artist; // 30 characters
private string _album; // 30 characters
private string _year; // 4 characters
private string _comment; // 28 characters, sometimes it's 30 characters when the next byte is not be 0 and this tag has not track information.
private string _reserved; // 1 byte, if it's 0 that means the next byte should contain which track on the CD this music comes from.
private int _track; // 1 byte, sometimes not exist if the reserved byte is not 0.
private int _genre = 12 ; // 1 byte
private string MP3Path; // mp3 file path
#endregion
#region Property
public ID3v1TagVersion TagVersion
{
get { return _tagVersion; }
set { _tagVersion = value; if (value == ID3v1TagVersion.ID3v11) { this .Comment = this ._comment; } }
}
public string Title
{
get { return _title; }
set { _title = GetString(value, 30 ); }
}
public string Artist
{
get { return _artist; }
set { _artist = GetString(value, 30 ); }
}
public string Album
{
get { return _album; }
set { _album = GetString(value, 30 ); }
}
public string Year
{
get { return _year; }
set { _year = GetString(value, 4 ); }
}
public string Comment
{
get { return _comment; }
set { _comment = GetString(value, this ._tagVersion == ID3v1TagVersion.ID3v10 ? 30 : 28 ); }
}
private string Reserved
{
get { return _reserved; }
set { _reserved = value; }
}
public int Track
{
get { return _track; }
set {
if (value >= 0 && value <= 0xff )
{
_track = value;
if ( this ._tagVersion == ID3v1TagVersion.ID3v10)
{
this .TagVersion = ID3v1TagVersion.ID3v11;
}
}
}
}
public int Genre
{
get { return _genre; }
set { _genre = value; }
}
#endregion
private ID3v1TagVersion _tagVersion;
private string _title; // 30 characters
private string _artist; // 30 characters
private string _album; // 30 characters
private string _year; // 4 characters
private string _comment; // 28 characters, sometimes it's 30 characters when the next byte is not be 0 and this tag has not track information.
private string _reserved; // 1 byte, if it's 0 that means the next byte should contain which track on the CD this music comes from.
private int _track; // 1 byte, sometimes not exist if the reserved byte is not 0.
private int _genre = 12 ; // 1 byte
private string MP3Path; // mp3 file path
#endregion
#region Property
public ID3v1TagVersion TagVersion
{
get { return _tagVersion; }
set { _tagVersion = value; if (value == ID3v1TagVersion.ID3v11) { this .Comment = this ._comment; } }
}
public string Title
{
get { return _title; }
set { _title = GetString(value, 30 ); }
}
public string Artist
{
get { return _artist; }
set { _artist = GetString(value, 30 ); }
}
public string Album
{
get { return _album; }
set { _album = GetString(value, 30 ); }
}
public string Year
{
get { return _year; }
set { _year = GetString(value, 4 ); }
}
public string Comment
{
get { return _comment; }
set { _comment = GetString(value, this ._tagVersion == ID3v1TagVersion.ID3v10 ? 30 : 28 ); }
}
private string Reserved
{
get { return _reserved; }
set { _reserved = value; }
}
public int Track
{
get { return _track; }
set {
if (value >= 0 && value <= 0xff )
{
_track = value;
if ( this ._tagVersion == ID3v1TagVersion.ID3v10)
{
this .TagVersion = ID3v1TagVersion.ID3v11;
}
}
}
}
public int Genre
{
get { return _genre; }
set { _genre = value; }
}
#endregion
读取ID3v1信息
private
void
ReadPath(
string
path)
{
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
this .ReadStream(stream);
}
}
private void ReadStream(Stream stream)
{
if (stream.Length >= 128 )
{
Encoding encode = Encoding.Default;
byte [] tag = new byte [ 128 ];
stream.Seek( - 128L , SeekOrigin.End);
stream.Read(tag, 0 , 128 );
if ( " TAG " == encode.GetString(tag, 0 , 3 ))
{
this ._title = encode.GetString(tag, 3 , 30 );
this ._artist = encode.GetString(tag, 33 , 30 );
this ._album = encode.GetString(tag, 63 , 30 );
this ._year = encode.GetString(tag, 93 , 4 );
if (tag[ 125 ] == 0 )
{
this ._tagVersion = ID3v1TagVersion.ID3v11;
this ._comment = encode.GetString(tag, 97 , 28 );
this ._track = tag[ 126 ];
}
else
{
this ._tagVersion = ID3v1TagVersion.ID3v10;
this ._comment = encode.GetString(tag, 97 , 30 );
this ._track = 0 ;
}
this ._genre = ( int )tag[ 127 ];
}
}
}
{
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
this .ReadStream(stream);
}
}
private void ReadStream(Stream stream)
{
if (stream.Length >= 128 )
{
Encoding encode = Encoding.Default;
byte [] tag = new byte [ 128 ];
stream.Seek( - 128L , SeekOrigin.End);
stream.Read(tag, 0 , 128 );
if ( " TAG " == encode.GetString(tag, 0 , 3 ))
{
this ._title = encode.GetString(tag, 3 , 30 );
this ._artist = encode.GetString(tag, 33 , 30 );
this ._album = encode.GetString(tag, 63 , 30 );
this ._year = encode.GetString(tag, 93 , 4 );
if (tag[ 125 ] == 0 )
{
this ._tagVersion = ID3v1TagVersion.ID3v11;
this ._comment = encode.GetString(tag, 97 , 28 );
this ._track = tag[ 126 ];
}
else
{
this ._tagVersion = ID3v1TagVersion.ID3v10;
this ._comment = encode.GetString(tag, 97 , 30 );
this ._track = 0 ;
}
this ._genre = ( int )tag[ 127 ];
}
}
}
保存ID3v1信息
private
void
Save(
string
path)
{
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
Save(stream);
}
}
private void Save(Stream stream)
{
byte [] header = SafeGetBytes( " TAG " );
byte [] title = SafeGetBytes( this ._title);
byte [] artist = SafeGetBytes( this ._artist);
byte [] album = SafeGetBytes( this ._album);
byte [] year = SafeGetBytes( this ._year);
byte [] comment = SafeGetBytes( this ._comment);
stream.Seek(( long ) - GetTagSize(stream), SeekOrigin.End);
stream.Write(header, 0 , 3 );
WriteBytesPadded(stream, title, 30 );
WriteBytesPadded(stream, artist, 30 );
WriteBytesPadded(stream, album, 30 );
WriteBytesPadded(stream, year, 4 );
if ( this ._tagVersion == ID3v1TagVersion.ID3v11)
{
WriteBytesPadded(stream, comment, 28 );
stream.WriteByte( 0 );
stream.WriteByte(( byte ) this ._track);
}
else
{
WriteBytesPadded(stream, comment, 30 );
}
stream.WriteByte(( byte ) this ._genre);
}
{
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
Save(stream);
}
}
private void Save(Stream stream)
{
byte [] header = SafeGetBytes( " TAG " );
byte [] title = SafeGetBytes( this ._title);
byte [] artist = SafeGetBytes( this ._artist);
byte [] album = SafeGetBytes( this ._album);
byte [] year = SafeGetBytes( this ._year);
byte [] comment = SafeGetBytes( this ._comment);
stream.Seek(( long ) - GetTagSize(stream), SeekOrigin.End);
stream.Write(header, 0 , 3 );
WriteBytesPadded(stream, title, 30 );
WriteBytesPadded(stream, artist, 30 );
WriteBytesPadded(stream, album, 30 );
WriteBytesPadded(stream, year, 4 );
if ( this ._tagVersion == ID3v1TagVersion.ID3v11)
{
WriteBytesPadded(stream, comment, 28 );
stream.WriteByte( 0 );
stream.WriteByte(( byte ) this ._track);
}
else
{
WriteBytesPadded(stream, comment, 30 );
}
stream.WriteByte(( byte ) this ._genre);
}
私有函数
private
static
string
GetString(
string
value,
int
maxLength)
{
if (value == null )
{
return null ;
}
value = value.Trim();
return value.Length > maxLength ? value.Substring( 0 , maxLength) : value;
}
private static int GetTagSize( string path)
{
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
return GetTagSize(stream);
}
return 0 ;
}
private static int GetTagSize(Stream stream)
{
if (stream.Length >= 128L )
{
byte [] header = new byte [ 3 ];
stream.Seek( - 128L , SeekOrigin.End);
stream.Read(header, 0 , 3 );
if (Encoding.Default.GetString(header) == " TAG " )
{
return 128 ;
}
}
return 0 ;
}
private static byte [] SafeGetBytes( string value)
{
if (value == null )
{
return new byte [ 0 ];
}
return Encoding.Default.GetBytes(value);
}
private static void WriteBytesPadded(Stream stream, byte [] buffer, int length)
{
int index = 0 ;
while ((index < length && index < buffer.Length) && buffer[index] != 0 )
{
stream.WriteByte(buffer[index]);
index ++ ;
}
while (index < length)
{
stream.WriteByte( 0 );
index ++ ;
}
}
{
if (value == null )
{
return null ;
}
value = value.Trim();
return value.Length > maxLength ? value.Substring( 0 , maxLength) : value;
}
private static int GetTagSize( string path)
{
using (FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
return GetTagSize(stream);
}
return 0 ;
}
private static int GetTagSize(Stream stream)
{
if (stream.Length >= 128L )
{
byte [] header = new byte [ 3 ];
stream.Seek( - 128L , SeekOrigin.End);
stream.Read(header, 0 , 3 );
if (Encoding.Default.GetString(header) == " TAG " )
{
return 128 ;
}
}
return 0 ;
}
private static byte [] SafeGetBytes( string value)
{
if (value == null )
{
return new byte [ 0 ];
}
return Encoding.Default.GetBytes(value);
}
private static void WriteBytesPadded(Stream stream, byte [] buffer, int length)
{
int index = 0 ;
while ((index < length && index < buffer.Length) && buffer[index] != 0 )
{
stream.WriteByte(buffer[index]);
index ++ ;
}
while (index < length)
{
stream.WriteByte( 0 );
index ++ ;
}
}
结语
有了这些就可以实现MP3的ID3v1信息的读取和修改了。
附录
流派信息有两部分,摘自ID3.ORG。若想知道其他流派信息,请自行搜索。
1. ID3v1定义的(流派前的数字是编号)
ID3v1定义
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
25 . Euro - Techno
26 . Ambient
27 . Trip - Hop
28 . Vocal
29 . Jazz + Funk
30 . Fusion
31 . Trance
32 . Classical
33 . Instrumental
34 . Acid
35 . House
36 . Game
37 . Sound Clip
38 . Gospel
39 . Noise
40 . AlternRock
41 . Bass
42 . Soul
43 . Punk
44 . Space
45 . Meditative
46 . Instrumental Pop
47 . Instrumental Rock
48 . Ethnic
49 . Gothic
50 . Darkwave
51 . Techno - Industrial
52 . Electronic
53 . Pop - Folk
54 . Eurodance
55 . Dream
56 . Southern Rock
57 . Comedy
58 . Cult
59 . Gangsta
60 . Top 40
61 . Christian Rap
62 . Pop / Funk
63 . Jungle
64 . Native American
65 . Cabaret
66 . New Wave
67 . Psychadelic
68 . Rave
69 . Showtunes
70 . Trailer
71 . Lo - Fi
72 . Tribal
73 . Acid Punk
74 . Acid Jazz
75 . Polka
76 . Retro
77 . Musical
78 . Rock & Roll
79 . Hard Rock
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
25 . Euro - Techno
26 . Ambient
27 . Trip - Hop
28 . Vocal
29 . Jazz + Funk
30 . Fusion
31 . Trance
32 . Classical
33 . Instrumental
34 . Acid
35 . House
36 . Game
37 . Sound Clip
38 . Gospel
39 . Noise
40 . AlternRock
41 . Bass
42 . Soul
43 . Punk
44 . Space
45 . Meditative
46 . Instrumental Pop
47 . Instrumental Rock
48 . Ethnic
49 . Gothic
50 . Darkwave
51 . Techno - Industrial
52 . Electronic
53 . Pop - Folk
54 . Eurodance
55 . Dream
56 . Southern Rock
57 . Comedy
58 . Cult
59 . Gangsta
60 . Top 40
61 . Christian Rap
62 . Pop / Funk
63 . Jungle
64 . Native American
65 . Cabaret
66 . New Wave
67 . Psychadelic
68 . Rave
69 . Showtunes
70 . Trailer
71 . Lo - Fi
72 . Tribal
73 . Acid Punk
74 . Acid Jazz
75 . Polka
76 . Retro
77 . Musical
78 . Rock & Roll
79 . Hard Rock
2. Winamp扩展的
Winamp扩展
80
. Folk
81 . Folk - Rock
82 . National Folk
83 . Swing
84 . Fast Fusion
85 . Bebob
86 . Latin
87 . Revival
88 . Celtic
89 . Bluegrass
90 . Avantgarde
91 . Gothic Rock
92 . Progressive Rock
93 . Psychedelic Rock
94 . Symphonic Rock
95 . Slow Rock
96 . Big Band
97 . Chorus
98 . Easy Listening
99 . Acoustic
100 . Humour
101 . Speech
102 . Chanson
103 . Opera
104 . Chamber Music
105 . Sonata
106 . Symphony
107 . Booty Bass
108 . Primus
109 . Porn Groove
110 . Satire
111 . Slow Jam
112 . Club
113 . Tango
114 . Samba
115 . Folklore
116 . Ballad
117 . Power Ballad
118 . Rhythmic Soul
119 . Freestyle
120 . Duet
121 . Punk Rock
122 . Drum Solo
123 . A capella
124 . Euro - House
125 . Dance Hall
81 . Folk - Rock
82 . National Folk
83 . Swing
84 . Fast Fusion
85 . Bebob
86 . Latin
87 . Revival
88 . Celtic
89 . Bluegrass
90 . Avantgarde
91 . Gothic Rock
92 . Progressive Rock
93 . Psychedelic Rock
94 . Symphonic Rock
95 . Slow Rock
96 . Big Band
97 . Chorus
98 . Easy Listening
99 . Acoustic
100 . Humour
101 . Speech
102 . Chanson
103 . Opera
104 . Chamber Music
105 . Sonata
106 . Symphony
107 . Booty Bass
108 . Primus
109 . Porn Groove
110 . Satire
111 . Slow Jam
112 . Club
113 . Tango
114 . Samba
115 . Folklore
116 . Ballad
117 . Power Ballad
118 . Rhythmic Soul
119 . Freestyle
120 . Duet
121 . Punk Rock
122 . Drum Solo
123 . A capella
124 . Euro - House
125 . Dance Hall