1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package entagged.audioformats.mp3.util;
20
21 import java.io.UnsupportedEncodingException;
22 import java.nio.ByteBuffer;
23 import java.util.Hashtable;
24
25 import entagged.audioformats.mp3.Id3v2Tag;
26 import entagged.audioformats.mp3.util.id3frames.ApicId3Frame;
27 import entagged.audioformats.mp3.util.id3frames.CommId3Frame;
28 import entagged.audioformats.mp3.util.id3frames.GenericId3Frame;
29 import entagged.audioformats.mp3.util.id3frames.Id3Frame;
30 import entagged.audioformats.mp3.util.id3frames.TextId3Frame;
31 import entagged.audioformats.mp3.util.id3frames.TimeId3Frame;
32 import entagged.audioformats.mp3.util.id3frames.UfidId3Frame;
33
34 /***
35 * This class parses an ID3V2 tag from a given {@link java.nio.ByteBuffer}.<br>
36 * It handles the versions 2,3 and 4.
37 *
38 * @author Rapha?l Slinckx , Christian Laireiter
39 */
40 public class Id3v24TagReader {
41
42 /***
43 * This field maps the field names of the version 2 frames to the one of
44 * version 3.<br>
45 */
46 private Hashtable conversion22to23;
47
48 /***
49 * Creates an instance.
50 *
51 */
52 public Id3v24TagReader() {
53 initConversionTable();
54 }
55
56 private String convertFromId3v22(String field) {
57 String s = (String) this.conversion22to23.get(field);
58
59 if (s == null)
60 return "";
61
62 return s;
63 }
64
65 private Id3Frame createId3Frame(String field, byte[] data, byte version)
66 throws UnsupportedEncodingException {
67 if (version == Id3v2Tag.ID3V22)
68 field = convertFromId3v22(field);
69
70
71 if (field.startsWith("T") && !field.startsWith("TX")) {
72 if (field.equalsIgnoreCase("TDRC")) {
73 return new TimeId3Frame(field, data, version);
74 }
75 return new TextId3Frame(field, data, version);
76 }
77
78 else if (field.startsWith("COMM"))
79 return new CommId3Frame(data, version);
80
81 else if (field.startsWith("UFID"))
82 return new UfidId3Frame(data, version);
83 else if (field.startsWith("APIC"))
84 return new ApicId3Frame(data, version);
85
86 else
87 return new GenericId3Frame(field, data, version);
88 }
89
90 /***
91 * This Method fills {@link #conversion}.
92 *
93 */
94 private void initConversionTable() {
95
96
97
98
99
100 this.conversion22to23 = new Hashtable(100);
101 String[] v22 = { "BUF", "CNT", "COM", "CRA", "CRM", "ETC", "EQU",
102 "GEO", "IPL", "LNK", "MCI", "MLL", "PIC", "POP", "REV", "RVA",
103 "SLT", "STC", "TAL", "TBP", "TCM", "TCO", "TCR", "TDA", "TDY",
104 "TEN", "TFT", "TIM", "TKE", "TLA", "TLE", "TMT", "TOA", "TOF",
105 "TOL", "TOR", "TOT", "TP1", "TP2", "TP3", "TP4", "TPA", "TPB",
106 "TRC", "TRD", "TRK", "TSI", "TSS", "TT1", "TT2", "TT3", "TXT",
107 "TXX", "TYE", "UFI", "ULT", "WAF", "WAR", "WAS", "WCM", "WCP",
108 "WPB", "WXX" };
109 String[] v23 = { "RBUF", "PCNT", "COMM", "AENC", "", "ETCO", "EQUA",
110 "GEOB", "IPLS", "LINK", "MCDI", "MLLT", "APIC", "POPM", "RVRB",
111 "RVAD", "SYLT", "SYTC", "TALB", "TBPM", "TCOM", "TCON", "TCOP",
112 "TDAT", "TDLY", "TENC", "TFLT", "TIME", "TKEY", "TLAN", "TLEN",
113 "TMED", "TOPE", "TOFN", "TOLY", "TORY", "TOAL", "TPE1", "TPE2",
114 "TPE3", "TPE4", "TPOS", "TPUB", "TSRC", "TRDA", "TRCK", "TSIZ",
115 "TSSE", "TIT1", "TIT2", "TIT3", "TEXT", "TXXX", "TYER", "UFID",
116 "USLT", "WOAF", "WOAR", "WOAS", "WCOM", "WCOP", "WPUB", "WXXX" };
117
118 for (int i = 0; i < v22.length; i++) {
119 this.conversion22to23.put(v22[i], v23[i]);
120 }
121 }
122
123 /***
124 * This method is used to skip the extended header in the reading process.
125 *
126 * @param data
127 * the buffer containing the extended header. (at current
128 * location)
129 * @param version
130 * the ID3V2 version {@link Id3v2Tag#ID3V22}.<br>
131 * @return the size of the extended Header. (skipping already performed)
132 */
133 private int processExtendedHeader(ByteBuffer data, byte version) {
134
135 int extsize = 0;
136 byte[] exthead = new byte[4];
137 data.get(exthead);
138 if (version == Id3v2Tag.ID3V23) {
139 extsize = readSize(data, Id3v2Tag.ID3V23);
140
141 data.position(data.position() + (extsize));
142 } else {
143 extsize = readSyncsafeInteger(data);
144 data.position(data.position() + (extsize));
145 }
146 return extsize;
147 }
148
149 /***
150 * This method reads an ID3V2 tag from the given {@link ByteBuffer} at its
151 * curren pointer location.<br>
152 *
153 * @param data
154 * ID3V2 tag.
155 * @param ID3Flags
156 * The flags of the tag header.
157 * @param version
158 * Version Flag. (used to handle some version specific
159 * implementations).
160 * @return An ID3V2 tag representation.
161 * @throws UnsupportedEncodingException
162 * Thrown on charset conversions, if system does not support
163 * them.
164 */
165 public Id3v2Tag read(ByteBuffer data, boolean[] ID3Flags, byte version)
166 throws UnsupportedEncodingException {
167
168 int tagSize = data.limit();
169 byte[] b;
170
171 Id3v2Tag tag = new Id3v2Tag();
172
173
174
175
176 if ((version == Id3v2Tag.ID3V23 || version == Id3v2Tag.ID3V24)
177 && ID3Flags[1]) {
178 processExtendedHeader(data, version);
179 }
180
181
182
183
184
185 int specSize = (version == Id3v2Tag.ID3V22) ? 3 : 4;
186
187 for (int a = 0; a < tagSize; a++) {
188
189 b = new byte[specSize];
190
191
192 if (data.remaining() <= specSize)
193 break;
194
195
196 data.get(b);
197
198
199 String field = new String(b);
200
201 if (b[0] == 0)
202 break;
203
204
205 int frameSize = readSize(data, version);
206
207
208
209 if ((frameSize > data.remaining()) || frameSize <= 0) {
210
211 System.err.println(field
212 + " Frame size error, skiping the rest of the tag:"
213 + frameSize);
214 break;
215 }
216
217 b = new byte[frameSize
218 + ((version == Id3v2Tag.ID3V23 || version == Id3v2Tag.ID3V24) ? 2
219 : 0)];
220
221 data.get(b);
222
223
224 if (!"".equals(field)) {
225 Id3Frame f = null;
226
227
228
229
230
231 try {
232
233 f = createId3Frame(field, b, version);
234 } catch (UnsupportedEncodingException uee) {
235 throw uee;
236 } catch (Exception e) {
237 e.printStackTrace();
238 }
239
240 if (f != null)
241 tag.add(f);
242 }
243 }
244
245 return tag;
246 }
247
248 /***
249 * This mehtod reads the data of an integer out of the given buffer.<br>
250 * Since different version of ID3V2 tags have a different size definiton,
251 * the version is needed.
252 *
253 * @param bb
254 * The buffer containing the integer.
255 * @param version
256 * The ID3V2 version. {@link Id3v2Tag#ID3V22}.
257 * @return The integer value
258 */
259 private int readSize(ByteBuffer bb, int version) {
260 int value = 0;
261 if (version == Id3v2Tag.ID3V24) {
262 value = readSyncsafeInteger(bb);
263 } else {
264 if (version == Id3v2Tag.ID3V23)
265 value += (bb.get() & 0xFF) << 24;
266 value += (bb.get() & 0xFF) << 16;
267 value += (bb.get() & 0xFF) << 8;
268 value += (bb.get() & 0xFF);
269 }
270 return value;
271 }
272
273 /***
274 * This method reads the next 4 byte of the buffer and interprets them as a
275 * sync safe integer.<br>
276 *
277 * @param buffer
278 * Buffer to read from
279 * @return represented integer value.
280 */
281 private int readSyncsafeInteger(ByteBuffer buffer) {
282 int value = 0;
283 value += (buffer.get() & 0xFF) << 21;
284 value += (buffer.get() & 0xFF) << 14;
285 value += (buffer.get() & 0xFF) << 7;
286 value += buffer.get() & 0xFF;
287 return value;
288 }
289 }