View Javadoc

1   /*
2    * Entagged Audio Tag library
3    * Copyright (c) 2003-2005 Raphaël Slinckx <raphael@slinckx.net>
4    * 
5    * This library is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *  
10   * This library is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   * 
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
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  				// Compression zlib, 4 bytes uncompressed size.
78  				size += 4;
79  			}
80  
81  			if ((raw[1] & 0x80) == 0x40) {
82  				// Encryption method byte
83  				size += 1;
84  			}
85  
86  			if ((raw[1] & 0x80) == 0x20) {
87  				// Group identity byte
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 			// 2 for BOM and 2 for terminal character
184 			byte[] tmp = new byte[result.length + 4];
185 			System.arraycopy(result, 0, tmp, 2, result.length);
186 			// Create the BOM
187 			tmp[0] = (byte) 0xFF;
188 			tmp[1] = (byte) 0xFE;
189 			result = tmp;
190 		} else {
191 			// this is encoding ISO-8859-1, for the time of this change.
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 		 * Create a copy in order to prevent developers from manipulating the
212 		 * frames data using this methods result.
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 	 * &quot;TCOM&quot;,&quot;TENC&quot;,&quot;TALB&quot; 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 &quot;litte endian&quot;.<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 			// do we have zero terminating chars (old entagged did not)
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 				 * Now we have a little problem. The tag is not id3-spec
313 				 * conform. And since I don't have a way to see if its little or
314 				 * big endian, i decide for the windows default little endian.
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, &quot;<code>-1</code>&quot; 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 		// Unsused
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 }