1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package entagged.audioformats.mp3.util.id3frames;
20
21 import java.io.UnsupportedEncodingException;
22 import java.util.Arrays;
23
24 import entagged.audioformats.generic.TagField;
25 import entagged.audioformats.mp3.Id3v2Tag;
26 import entagged.audioformats.mp3.util.Id3v2TagCreator;
27
28 /***
29 * This class represents an ID3-Frame and provides basic information.<br>
30 *
31 * @author Raphaël Slinckx
32 * @author Christian Laireiter
33 */
34 public abstract class Id3Frame implements TagField {
35
36 /***
37 * The frame header flags are stored here.<br>
38 * The flags have always been 2 bytes long. (Id3 version 2.x).
39 */
40 protected byte[] flags;
41
42 /***
43 * ID3 version identifier.<br>
44 *
45 * @see Id3v2Tag#ID3V22
46 * @see Id3v2Tag#ID3V23
47 * @see Id3v2Tag#ID3V24
48 */
49 protected byte version;
50
51 /***
52 * Creates a new frame instance.<br>
53 * Id3 version assigned is {@linkplain Id3v2Tag#ID3V24 2.4}.<br>
54 */
55 public Id3Frame() {
56 this.version = Id3v2Tag.ID3V24;
57 createDefaultFlags();
58 }
59
60 /***
61 * Creates a frame instance which adjusts to the given <code>raw</code>-data.<br>
62 *
63 * @param raw
64 * The data of the frame.
65 * @param id3Version
66 * The ID3-Tag version.
67 * @throws UnsupportedEncodingException
68 * If a textframe's content cannot be interpreted.
69 */
70 public Id3Frame(byte[] raw, byte id3Version)
71 throws UnsupportedEncodingException {
72 byte[] rawNew;
73 if (id3Version == Id3v2Tag.ID3V23 || id3Version == Id3v2Tag.ID3V24) {
74 byte size = 2;
75
76 if ((raw[1] & 0x80) == 0x80) {
77
78 size += 4;
79 }
80
81 if ((raw[1] & 0x80) == 0x40) {
82
83 size += 1;
84 }
85
86 if ((raw[1] & 0x80) == 0x20) {
87
88 size += 1;
89 }
90
91 this.flags = new byte[size];
92 for (int i = 0; i < size; i++)
93 this.flags[i] = raw[i];
94 rawNew = raw;
95 } else {
96 createDefaultFlags();
97 rawNew = new byte[this.flags.length + raw.length];
98 copy(this.flags, rawNew, 0);
99 copy(raw, rawNew, this.flags.length);
100 }
101
102 this.version = id3Version;
103
104 populate(rawNew);
105 }
106
107 /***
108 * This method creates a binary representation of the current
109 * {@link Id3Frame} data.<br>
110 * This data can directly be written to a file or stream.<br>
111 * s
112 *
113 * @return Binary representation of current frame based on the
114 * implementation (what does it stand for) and data (its contents).<br>
115 * @throws UnsupportedEncodingException
116 * If a text-Frame is constructed there can occur an error
117 * during text conversions.<br>
118 */
119 protected abstract byte[] build() throws UnsupportedEncodingException;
120
121 /***
122 * (overridden)
123 *
124 * @see java.lang.Object#clone()
125 */
126 public Object clone() throws CloneNotSupportedException {
127 return super.clone();
128 }
129
130 protected void copy(byte[] src, byte[] dst, int dstOffset) {
131 for (int i = 0; i < src.length; i++)
132 dst[i + dstOffset] = src[i];
133 }
134
135 /***
136 * Fills {@link #flags} with two zero bytes.
137 */
138 private void createDefaultFlags() {
139 this.flags = new byte[2];
140 this.flags[0] = 0;
141 this.flags[1] = 0;
142 }
143
144 /***
145 * (overridden) <br>
146 * For Id3Frame objects the comparison can be easily done by comparing their
147 * binary representation which can be retrieved by invoking
148 * {@link Id3Frame#build()}.<br>
149 *
150 * @see java.lang.Object#equals(java.lang.Object)
151 */
152 public boolean equals(Object obj) {
153 if (obj instanceof Id3Frame) {
154 Id3Frame other = (Id3Frame) obj;
155 try {
156 return Arrays.equals(this.build(), other.build());
157 } catch (UnsupportedEncodingException e) {
158 e.printStackTrace();
159 }
160 }
161 return false;
162 }
163
164 /***
165 * Convenience method converting a string to a binary representation
166 * according to the given encoding.<br>
167 *
168 * @param s
169 * The string to get the binary representation from.
170 * @param encoding
171 * The encoding which should be used.
172 * @return Binary representation of the given string in the given encoding
173 * with BOM for UTF and the terminating character(s).
174 * @throws UnsupportedEncodingException
175 * If the string needs to be converted and the encoding is not
176 * present.
177 */
178 protected byte[] getBytes(String s, String encoding)
179 throws UnsupportedEncodingException {
180 byte[] result = null;
181 if ("UTF-16".equalsIgnoreCase(encoding)) {
182 result = s.getBytes("UTF-16LE");
183
184 byte[] tmp = new byte[result.length + 4];
185 System.arraycopy(result, 0, tmp, 2, result.length);
186
187 tmp[0] = (byte) 0xFF;
188 tmp[1] = (byte) 0xFE;
189 result = tmp;
190 } else {
191
192 result = s.getBytes(encoding);
193 int zeroTerm = 1;
194 if ("UTF-16BE".equals(encoding)) {
195 zeroTerm = 2;
196 }
197 byte[] tmp = new byte[result.length + zeroTerm];
198 System.arraycopy(result, 0, tmp, 0, result.length);
199 result = tmp;
200 }
201 return result;
202 }
203
204 /***
205 * Returns the flags of the frame.<br>
206 *
207 * @return The flag bytes.
208 */
209 public byte[] getFlags() {
210
211
212
213
214 byte[] result = new byte[this.flags.length];
215 System.arraycopy(this.flags, 0, result, 0, result.length);
216 return result;
217 }
218
219 /***
220 * (overridden)<br>
221 * For Id3-Frames its something like
222 * "TCOM","TENC","TALB" and so on.
223 *
224 * @see entagged.audioformats.generic.TagField#getId()
225 */
226 public abstract String getId();
227
228 /***
229 * Convenience method for getting the binary representation of
230 * {@link #getId()}.<br>
231 *
232 * @see String#getBytes()
233 *
234 * @return The bytes of {@link #getId()}.
235 */
236 protected byte[] getIdBytes() {
237 return getId().getBytes();
238 }
239
240 /***
241 * (overridden) <br>
242 * simply calls {@link #build()} directly.
243 *
244 * @see entagged.audioformats.generic.TagField#getRawContent()
245 */
246 public byte[] getRawContent() throws UnsupportedEncodingException {
247 return build();
248 }
249
250 /***
251 * Converts the given value of <code>size</code> into a representation
252 * which can be used to write a frames size into a file (or stream).<br>
253 * For now there are two different kinds of binary representation of a size
254 * value in ID3 frames.<br>
255 * First the prior 2.4 variation which is a simple "litte endian".<br>
256 * Second the 2.4 variation which utilizes
257 * {@link Id3v2TagCreator#getSyncSafe(int)}.<br>
258 *
259 * @param size
260 * The integer value to convert.
261 * @return The binary representation according to the ID3 version.<br>
262 */
263 protected byte[] getSize(int size) {
264 byte[] b = null;
265 if (this.version == Id3v2Tag.ID3V24) {
266 b = Id3v2TagCreator.getSyncSafe(size);
267 } else {
268 b = new byte[4];
269 b[0] = (byte) ((size >> 24) & 0xFF);
270 b[1] = (byte) ((size >> 16) & 0xFF);
271 b[2] = (byte) ((size >> 8) & 0xFF);
272 b[3] = (byte) (size & 0xFF);
273 }
274 return b;
275 }
276
277 /***
278 * Another convenience Method which parses a byte array and returns a
279 * {@link String} instance.<br>
280 *
281 * @param b
282 * The array where the string resides.
283 * @param offset
284 * The offset in <code>b</code> where the string begins.
285 * @param length
286 * The length in bytes which makes up the string.
287 * @param encoding
288 * The encoding of the string in <code>b</code>.
289 * @return A String representation of the specified data.
290 * @throws UnsupportedEncodingException
291 * If an conversion error occurs or the encoding is not
292 * available on the running system.
293 */
294 protected String getString(byte[] b, int offset, int length, String encoding)
295 throws UnsupportedEncodingException {
296 String result = null;
297 if ("UTF-16".equalsIgnoreCase(encoding)) {
298 int zerochars = 0;
299
300 if (b[offset + length - 2] == 0x00
301 && b[offset + length - 1] == 0x00) {
302 zerochars = 2;
303 }
304 if (b[offset] == (byte) 0xFE && b[offset + 1] == (byte) 0xFF) {
305 result = new String(b, offset + 2, length - 2 - zerochars,
306 "UTF-16BE");
307 } else if (b[offset] == (byte) 0xFF && b[offset + 1] == (byte) 0xFE) {
308 result = new String(b, offset + 2, length - 2 - zerochars,
309 "UTF-16LE");
310 } else {
311
312
313
314
315
316 result = new String(b, offset, length - zerochars, "UTF-16LE");
317 }
318 } else {
319 int zerochars = 0;
320 if ("UTF-16BE".equals(encoding)) {
321 if (b[offset + length - 2] == 0x00
322 && b[offset + length - 1] == 0x00) {
323 zerochars = 2;
324 }
325 } else if (b[offset + length - 1] == 0x00) {
326 zerochars = 1;
327 }
328 if (length == 0 || offset + length > b.length) {
329 result = "";
330 } else {
331 result = new String(b, offset, length - zerochars, encoding);
332 }
333 }
334 return result;
335 }
336
337 /***
338 * Convenience method which searches for the next zero byte in an array.
339 *
340 * @param b
341 * The array to search in.
342 * @param offset
343 * The offset of <b>b</b> from where to look for the next zero
344 * byte.
345 * @return The index of the zero byte in <code>b</code> if found. If none
346 * was found, "<code>-1</code>" is returned.
347 */
348 protected int indexOfFirstNull(byte[] b, int offset) {
349 for (int i = offset; i < b.length; i++)
350 if (b[i] == 0)
351 return i;
352 return -1;
353 }
354
355 /***
356 * (overridden)
357 *
358 * @see entagged.audioformats.generic.TagField#isBinary()
359 */
360 public abstract boolean isBinary();
361
362 /***
363 * (overridden)
364 *
365 * @see entagged.audioformats.generic.TagField#isBinary(boolean)
366 */
367 public void isBinary(boolean b) {
368
369 }
370
371 /***
372 * (overridden)
373 *
374 * @see entagged.audioformats.generic.TagField#isCommon()
375 */
376 public abstract boolean isCommon();
377
378 /***
379 * This method reads the given data of an ID3-Frame and interprets it
380 * implementation specific.<br>
381 * The values of the Id3Frame instance are adjusted.
382 *
383 * @param raw
384 * The frame data.
385 * @throws UnsupportedEncodingException
386 * On text frames there can be such errors.<br>
387 */
388 protected abstract void populate(byte[] raw)
389 throws UnsupportedEncodingException;
390 }