root/trunk/id3tag.d

Revision 14, 5.9 kB (checked in by Jaymz031602, 4 years ago)

--

Line 
1 // ID3 Tag retrieval D module.
2 // written by James Dunne
3
4 // (c) Copyright James Dunne 2004, 2005
5 // 06 Dec. 2004
6
7 module  id3tag;
8
9 import  std.stream;
10 import  std.intrinsic;
11 import  std.string;
12
13 // A very simple tag structure, only
14 // concerned with simple song details.
15 struct ID3Tag {
16     // 1 for ID3v1, 2 for ID3v2
17     int     id3version;
18     // The common fields:
19     char[]  title;
20     char[]  artist;
21     char[]  album;
22     int     year;
23     int     track, tracks;
24     char[]  genre;
25 };
26
27 // Exception thrown by readID3Tag():
28 class ID3Exception : Exception {
29     this(char[] msg) {
30         super(msg);
31     }
32 }
33
34 // Reads an ID3 tag from an MP3 stream.
35 // Stream must be at least both seekable and readable.
36
37 // This function returns an ID3Tag structure representing the
38 // ID3 information read in.  ID3v2 tag is searched for first,
39 // and if not found, ID3v1 is searched for.  If neither is found
40 // the function returns 'null'.
41
42 ID3Tag* readID3Tag(Stream stream) {
43     ID3Tag* tag;
44
45     // Read a string from a stream:
46     char[] readString(Stream stream, int taglen) {
47         // Read the string:
48         char*   str = new char[taglen];
49         stream.readBlock(cast(void*)str, taglen);
50
51         return str[0 .. taglen].dup;
52     }
53
54     // Test stream properties:
55     if (!stream.seekable) throw new ID3Exception("Stream must be seekable");
56     if (!stream.readable) throw new ID3Exception("Stream must be readable");
57
58     char*   blk = new char[4];
59     const char[3]   head = "ID3";
60
61     // Look for an ID3 v2 tag at the start of the stream:
62     stream.seek(0x00, SeekPos.Set);
63     stream.readBlock(cast(void*)blk, 4);
64
65     if ((blk[0 .. 3] == head) && (blk[3] == 3)) {
66         // The code below is based totally on my hacking ability, and I know it
67         // to be incorrect for certain ID3v2 layouts.  I have yet to hack out
68         // that format.
69
70         bool    done = false;
71         uint    taglen;
72         ushort  dummy;
73
74         tag = new ID3Tag;
75         tag.id3version = 2;
76
77         // Skip 6 bytes:
78         stream.seek(0x06, SeekPos.Current);
79
80         // Keep reading blocks of 4-character identifiers:
81         while (!done) {
82             // Read the block identifier:
83             stream.readBlock(cast(void*)blk, 4);
84             if (blk[0] == 0) break;
85
86             // Read the length:
87             stream.readBlock(cast(void*)&taglen, 4);
88             version (LittleEndian) taglen = bswap(taglen);
89             // Read 2 dumb bytes:
90             stream.readBlock(cast(void*)&dummy, 2);
91             // dummy should be byte-swapped also (only as a ushort)
92             // however, it is never used.
93
94             // Now, what to do with the string?
95             switch (blk[0 .. 4]) {
96                 // Song title:
97                 case "TIT2":
98                     stream.seek(1, SeekPos.Current); --taglen;
99                     tag.title = readString(stream, taglen);
100                     break;
101                 // Song artist:
102                 case "TPE1", "TPE2":
103                     stream.seek(1, SeekPos.Current); --taglen;
104                     tag.artist = readString(stream, taglen);
105                     break;
106                 // Album title:
107                 case "TALB":
108                     stream.seek(1, SeekPos.Current); --taglen;
109                     tag.album = readString(stream, taglen);
110                     break;
111                 // Song genre:
112                 case "TCON":
113                     stream.seek(1, SeekPos.Current); --taglen;
114                     tag.genre = readString(stream, taglen);
115                     break;
116                 // Song track:  (can also be "n/N" format where n is track and N is tracks)
117                 case "TRCK":
118                     stream.seek(1, SeekPos.Current); --taglen;
119                     char[][] trkstrs = split(readString(stream, taglen), "/");
120                     tag.track = atoi(trkstrs[0]);
121                     // If no / separator, just set a dumb default for maxtracks:
122                     if (trkstrs.length == 2)
123                         tag.tracks = atoi(trkstrs[1]);
124                     else
125                         tag.tracks = 0;
126                     break;
127                 // Album year release:
128                 case "TYER":
129                     stream.seek(1, SeekPos.Current); --taglen;
130                     tag.year = atoi(readString(stream, taglen));
131                     break;
132
133                 // Dummy fields ignored:
134                 default:
135                     stream.seek(taglen, SeekPos.Current);
136                     break;
137             }
138         }
139         return tag;
140
141     } else {
142
143         // Look for an ID3 v1 tag 0x80 bytes from end of stream:
144         stream.seek(-0x80, SeekPos.End);
145         stream.readBlock(cast(void*)blk, 3);
146
147         if (blk[0 .. 3] == "TAG") {
148             char*   str = new char[30];
149             ubyte   trk = 0;
150
151             tag = new ID3Tag;
152             tag.id3version = 1;
153
154             // All fields are 30 characters long:
155             stream.readBlock(cast(void*)str, 30);
156             tag.title = strip(std.string.toString(str));
157
158             str = new char[30];
159             stream.readBlock(cast(void*)str, 30);
160             tag.artist = strip(std.string.toString(str));
161
162             str = new char[30];
163             stream.readBlock(cast(void*)str, 30);
164             tag.album = strip(std.string.toString(str));
165
166             str = new char[4];
167             stream.readBlock(cast(void*)str, 4);
168             tag.year = atoi(str[0 .. 4]);
169
170             // I'm not 100% sure, but this seems to be the case:
171             stream.seek(-1, SeekPos.End);
172             stream.readBlock(cast(void*)&trk, 1);
173             tag.track = trk;
174             // No max tracks provided in ID3v1:
175             tag.tracks = 0;
176
177             // Who knows after this?
178             return tag;
179
180         } else {
181
182             // No ID3v1 or ID3v2 tag found.
183
184             // Please note that I do NOT throw an exception here,
185             // since it is NOT an error that an ID3 tag is not found.
186             // Some files simply do not contain them.  Also, there is
187             // the possibility that the stream being scanned is NOT
188             // an MP3 file.
189
190             return null;
191
192         }
193     }
194
195     return null;
196 }
197
198 // A dummy test:
199 debug {
200     int main(char[][] args) {
201         // Check command arguments:
202         if (args.length < 2) {
203             printf("%.*s <filename.mp3>\n", args[0]);
204             return 1;
205         }
206
207         // Open the mp3 file:
208         File    mp3 = new File(args[1]);
209
210         // Search for ID3 tag (v2 first, then v1):
211         ID3Tag* tag = readID3Tag(mp3);
212         if (!(tag is null)) {
213             printf("ID3v%d tag\n", tag.id3version);
214             printf("Title:      '%.*s'\n", tag.title);
215             printf("Arist:      '%.*s'\n", tag.artist);
216             printf("Album:      '%.*s'\n", tag.album);
217             printf("Album Year: %d\n", tag.year);
218             printf("Track:      %d/%d\n", tag.track, tag.tracks);
219             printf("Genre:      '%.*s'\n", tag.genre);
220         } else {
221             printf("No ID3 tag found\n");
222         }
223
224         // Close that mp3 file:
225         mp3.close();
226
227         // That's it!
228         return 0;
229     }
230 }
Note: See TracBrowser for help on using the browser.