1 | package org.farng.mp3.id3; |
2 | |
3 | import org.farng.mp3.InvalidTagException; |
4 | import org.farng.mp3.TagConstant; |
5 | import org.farng.mp3.TagUtility; |
6 | import org.farng.mp3.lyrics3.FieldBodyAUT; |
7 | import org.farng.mp3.lyrics3.FieldBodyEAL; |
8 | import org.farng.mp3.lyrics3.FieldBodyEAR; |
9 | import org.farng.mp3.lyrics3.FieldBodyETT; |
10 | import org.farng.mp3.lyrics3.FieldBodyINF; |
11 | import org.farng.mp3.lyrics3.FieldBodyLYR; |
12 | import org.farng.mp3.lyrics3.Lyrics3v2Field; |
13 | import org.farng.mp3.object.ObjectLyrics3Line; |
14 | |
15 | import java.io.IOException; |
16 | import java.io.RandomAccessFile; |
17 | import java.util.Iterator; |
18 | |
19 | /** |
20 | * <p> All ID3v2 frames consists of one frame header followed by one or more<br> fields |
21 | * containing the actual information. The header is always 10<br> bytes and laid out as follows:</p> |
22 | * <p/> |
23 | * <p> Frame ID $xx xx xx xx (four characters)<br> |
24 | * Size 4 * %0xxxxxxx<br> |
25 | * Flags $xx xx</p> |
26 | * <p/> |
27 | * <p> The frame ID is made out of the characters capital A-Z and 0-9.<br> Identifiers |
28 | * beginning with "X", "Y" and "Z" are for experimental<br> frames and free |
29 | * for everyone to use, without the need to set the<br> experimental bit in the tag header. Bear in mind |
30 | * that someone else<br> might have used the same identifier as you. All other identifiers are<br> |
31 | * either used or reserved for future use.</p> |
32 | * <p/> |
33 | * <p> The frame ID is followed by a size descriptor containing the size of<br> the data in the |
34 | * final frame, after encryption, compression and<br> unsynchronisation. The size is excluding the frame |
35 | * header ('total<br> frame size' - 10 bytes) and stored as a 32 bit synchsafe integer.</p> |
36 | * <p/> |
37 | * <p> In the frame header the size descriptor is followed by two flag<br> bytes. These flags |
38 | * are described in section 4.1.</p> |
39 | * <p/> |
40 | * <p> There is no fixed order of the frames' appearance in the tag,<br> although it is desired |
41 | * that the frames are arranged in order of<br> significance concerning the recognition of the file. An |
42 | * example of<br> such order: UFID, TIT2, MCDI, TRCK ...</p> |
43 | * <p/> |
44 | * <p> A tag MUST contain at least one frame. A frame must be at least 1<br> byte big, |
45 | * excluding the header.</p> |
46 | * <p/> |
47 | * <p> If nothing else is said, strings, including numeric strings and URLs<br> [URL], are |
48 | * represented as ISO-8859-1 [ISO-8859-1] characters in the<br> range $20 - $FF. Such strings are |
49 | * represented in frame descriptions<br> as <text string>, or <full text string> if newlines |
50 | * are allowed. If<br> nothing else is said newline character is forbidden. In ISO-8859-1 a<br> |
51 | * newline is represented, when allowed, with $0A only.</p> |
52 | * <p/> |
53 | * <p> Frames that allow different types of text encoding contains a text<br> encoding |
54 | * description byte. Possible encodings:</p> |
55 | * <p/> |
56 | * <p> $00 ISO-8859-1 [ISO-8859-1]. Terminated with $00.<br> |
57 | * $01 UTF-16 [UTF-16] encoded Unicode [UNICODE] with BOM. All<br> |
58 | * strings in the same frame SHALL have the same |
59 | * byteorder.<br> Terminated with $00 00.<br> |
60 | * $02 UTF-16BE [UTF-16] encoded Unicode [UNICODE] without BOM.<br> |
61 | * Terminated with $00 00.<br> |
62 | * $03 UTF-8 [UTF-8] encoded Unicode [UNICODE]. Terminated with $00.</p> |
63 | * <p/> |
64 | * <p> Strings dependent on encoding are represented in frame descriptions<br> as <text |
65 | * string according to encoding>, or <full text string<br> according to encoding> if newlines are |
66 | * allowed. Any empty strings of<br> type $01 which are NULL-terminated may have the Unicode BOM |
67 | * followed<br> by a Unicode NULL ($FF FE 00 00 or $FE FF 00 00).</p> |
68 | * <p/> |
69 | * <p> The timestamp fields are based on a subset of ISO 8601. When being as<br> precise as |
70 | * possible the format of a time string is<br> yyyy-MM-ddTHH:mm:ss (year, "-", month, |
71 | * "-", day, "T", hour (out of<br> 24), ":", minutes, ":", |
72 | * seconds), but the precision may be reduced by<br> removing as many time indicators as wanted. Hence |
73 | * valid timestamps<br> are<br> yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddTHH, yyyy-MM-ddTHH:mm |
74 | * and<br> yyyy-MM-ddTHH:mm:ss. All time stamps are UTC. For durations, use<br> the slash |
75 | * character as described in 8601, and for multiple non-<br> contiguous dates, use multiple strings, if |
76 | * allowed by the frame<br> definition.</p> |
77 | * <p/> |
78 | * <p> The three byte language field, present in several frames, is used to<br> describe the |
79 | * language of the frame's content, according to ISO-639-2<br> [ISO-639-2]. The language should be |
80 | * represented in lower case. If the<br> language is not known the string "XXX" should be |
81 | * used.</p> |
82 | * <p/> |
83 | * <p> All URLs [URL] MAY be relative, e.g. "picture.png", "../doc.txt".</p> |
84 | * <p/> |
85 | * <p> If a frame is longer than it should be, e.g. having more fields than<br> specified in |
86 | * this document, that indicates that additions to the<br> frame have been made in a later version of the |
87 | * ID3v2 standard. This<br> is reflected by the revision number in the header of the tag.<br> </p> <a |
88 | * name="sec4.1"></a> |
89 | * <p/> |
90 | * <h3>4.1. Frame header flags</h3> |
91 | * <p/> |
92 | * <p> In the frame header the size descriptor is followed by two flag<br> bytes. All unused |
93 | * flags MUST be cleared. The first byte is for<br> 'status messages' and the second byte is a format |
94 | * description. If an<br> unknown flag is set in the first byte the frame MUST NOT be changed<br> |
95 | * without that bit cleared. If an unknown flag is set in the second<br> byte the frame is |
96 | * likely to not be readable. Some flags in the second<br> byte indicates that extra information is added |
97 | * to the header. These<br> fields of extra information is ordered as the flags that indicates<br> |
98 | * them. The flags field is defined as follows (l and o left out because<br> ther resemblence |
99 | * to one and zero):</p> |
100 | * <p/> |
101 | * <p> %0abc0000 %0h00kmnp</p> |
102 | * <p/> |
103 | * <p> Some frame format flags indicate that additional information fields<br> are added to the |
104 | * frame. This information is added after the frame<br> header and before the frame data in the same order |
105 | * as the flags that<br> indicates them. I.e. the four bytes of decompressed size will precede<br> |
106 | * the encryption method byte. These additions affects the 'frame size'<br> field, but are not |
107 | * subject to encryption or compression.<br> </p> |
108 | * <p/> |
109 | * <p> The default status flags setting for a frame is, unless stated<br> otherwise, 'preserved |
110 | * if tag is altered' and 'preserved if file is<br> altered', i.e. %00000000.<br> </p> <a |
111 | * name="sec4.1.1"></a> |
112 | * <p/> |
113 | * <h3>4.1.1. Frame status flags</h3> |
114 | * <p/> |
115 | * <p> a - Tag alter preservation</p> |
116 | * <p/> |
117 | * <p> This flag tells the tag parser what to do with this frame if it is<br> |
118 | * unknown and the tag is altered in any way. This applies to all<br> |
119 | * kinds of alterations, including adding more padding and reordering<br> the frames.</p> |
120 | * <p/> |
121 | * <p> 0 Frame should be preserved.<br> |
122 | * 1 Frame should be discarded.<br> </p> |
123 | * <p/> |
124 | * <p> b - File alter preservation</p> |
125 | * <p/> |
126 | * <p> This flag tells the tag parser what to do with this frame if it is<br> |
127 | * unknown and the file, excluding the tag, is altered. This does not<br> |
128 | * apply when the audio is completely replaced with other audio data.</p> |
129 | * <p/> |
130 | * <p> 0 Frame should be preserved.<br> |
131 | * 1 Frame should be discarded.<br> </p> |
132 | * <p/> |
133 | * <p> c - Read only</p> |
134 | * <p/> |
135 | * <p> This flag, if set, tells the software that the contents of this<br> |
136 | * frame are intended to be read only. Changing the contents might<br> |
137 | * break something, e.g. a signature. If the contents are changed,<br> |
138 | * without knowledge of why the frame was flagged read only and<br> |
139 | * without taking the proper means to compensate, e.g. recalculating<br> |
140 | * the signature, the bit MUST be cleared.<br> </p> <a name="sec4.1.2"></a> |
141 | * <p/> |
142 | * <h3>4.1.2. Frame format flags</h3> |
143 | * <p/> |
144 | * <p> h - Grouping identity</p> |
145 | * <p/> |
146 | * <p> This flag indicates whether or not this frame belongs in a group<br> |
147 | * with other frames. If set, a group identifier byte is added to the<br> |
148 | * frame. Every frame with the same group identifier belongs to the<br> |
149 | * same group.</p> |
150 | * <p/> |
151 | * <p> 0 Frame does not contain group information<br> |
152 | * 1 Frame contains group information<br> </p> |
153 | * <p/> |
154 | * <p> k - Compression</p> |
155 | * <p/> |
156 | * <p> This flag indicates whether or not the frame is compressed.<br> |
157 | * A 'Data Length Indicator' byte MUST be included in the frame.</p> |
158 | * <p/> |
159 | * <p> 0 Frame is not compressed.<br> |
160 | * 1 Frame is compressed using zlib [zlib] deflate method.<br> |
161 | * If set, this requires the 'Data Length Indicator' |
162 | * bit<br> to be set as well.<br> </p> |
163 | * <p/> |
164 | * <p> m - Encryption<br> </p> |
165 | * <p/> |
166 | * <p> This flag indicates whether or not the frame is encrypted. If set,<br> |
167 | * one byte indicating with which method it was encrypted will be<br> |
168 | * added to the frame. See description of the ENCR frame for more<br> |
169 | * information about encryption method registration. Encryption<br> |
170 | * should be done after compression. Whether or not setting this flag<br> |
171 | * requires the presence of a 'Data Length Indicator' depends on the<br> |
172 | * specific algorithm used.</p> |
173 | * <p/> |
174 | * <p> 0 Frame is not encrypted.<br> |
175 | * 1 Frame is encrypted.</p> |
176 | * <p/> |
177 | * <p> n - Unsynchronisation</p> |
178 | * <p/> |
179 | * <p> This flag indicates whether or not unsynchronisation was applied<br> |
180 | * to this frame. See section 6 for details on unsynchronisation.<br> |
181 | * If this flag is set all data from the end of this header to the<br> |
182 | * end of this frame has been unsynchronised. Although desirable, the<br> |
183 | * presence of a 'Data Length Indicator' is not made mandatory by<br> |
184 | * unsynchronisation.</p> |
185 | * <p/> |
186 | * <p> 0 Frame has not been unsynchronised.<br> |
187 | * 1 Frame has been unsyrchronised.</p> |
188 | * <p/> |
189 | * <p> p - Data length indicator</p> |
190 | * <p/> |
191 | * <p> This flag indicates that a data length indicator has been added to<br> |
192 | * the frame. The data length indicator is the value one would write<br> |
193 | * as the 'Frame length' if all of the frame format flags were<br> |
194 | * zeroed, represented as a 32 bit synchsafe integer.</p> |
195 | * <p/> |
196 | * <p> 0 There is no Data Length Indicator.<br> |
197 | * 1 A data length Indicator has been added to the |
198 | * frame.<br> </p> |
199 | * |
200 | * @author Eric Farng |
201 | * @version $Revision: 1.6 $ |
202 | */ |
203 | public class ID3v2_4Frame extends ID3v2_3Frame { |
204 | |
205 | protected boolean dataLengthIndicator = false; |
206 | protected boolean unsynchronization = false; |
207 | |
208 | /** |
209 | * Creates a new ID3v2_4Frame object. |
210 | */ |
211 | public ID3v2_4Frame() { |
212 | // base empty constructor |
213 | } |
214 | |
215 | /** |
216 | * Creates a new ID3v2_4Frame object. |
217 | */ |
218 | public ID3v2_4Frame(final ID3v2_4Frame copyObject) { |
219 | super(copyObject); |
220 | this.dataLengthIndicator = copyObject.dataLengthIndicator; |
221 | this.unsynchronization = copyObject.unsynchronization; |
222 | } |
223 | |
224 | /** |
225 | * Creates a new ID3v2_4Frame object. |
226 | */ |
227 | public ID3v2_4Frame(final AbstractID3v2FrameBody body) { |
228 | super(body); |
229 | } |
230 | |
231 | /** |
232 | * Creates a new ID3v2_4Frame object. |
233 | */ |
234 | public ID3v2_4Frame(final AbstractID3v2Frame frame) { |
235 | if (frame instanceof ID3v2_4Frame) { |
236 | final ID3v2_4Frame f = (ID3v2_4Frame) frame; |
237 | this.unsynchronization = f.unsynchronization; |
238 | this.dataLengthIndicator = f.dataLengthIndicator; |
239 | } |
240 | if (frame instanceof ID3v2_3Frame) { |
241 | // a id3v2_4 frame is of type id3v2_3 frame also ... |
242 | final ID3v2_3Frame f = (ID3v2_3Frame) frame; |
243 | this.tagAlterPreservation = f.tagAlterPreservation; |
244 | this.fileAlterPreservation = f.fileAlterPreservation; |
245 | this.readOnly = f.readOnly; |
246 | this.groupingIdentity = f.groupingIdentity; |
247 | this.compression = f.compression; |
248 | this.encryption = f.encryption; |
249 | } |
250 | if (frame instanceof ID3v2_2Frame) { |
251 | // no variables yet |
252 | } |
253 | if (frame.getBody() == null) { |
254 | // do nothing |
255 | } else if (TagUtility.isID3v2_4FrameIdentifier(frame.getIdentifier())) { |
256 | this.setBody((AbstractID3v2FrameBody) TagUtility.copyObject(frame.getBody())); |
257 | // } else if (TagUtility.isID3v2_3FrameIdentifier(frame.getIdentifier())) { |
258 | // // @TODO correctly convert tags |
259 | // this.setBody((AbstractID3v2FrameBody) TagUtility.copyObject(frame.getBody())); |
260 | // } else if (TagUtility.isID3v2_2FrameIdentifier(frame.getIdentifier())) { |
261 | // // @TODO correctly convert tags |
262 | // this.setBody((AbstractID3v2FrameBody) TagUtility.copyObject(frame.getBody())); |
263 | } |
264 | } |
265 | |
266 | /** |
267 | * Creates a new ID3v2_4Frame object. |
268 | */ |
269 | public ID3v2_4Frame(final boolean readOnly, |
270 | final boolean groupingIdentity, |
271 | final boolean compression, |
272 | final boolean encryption, |
273 | final boolean unsynchronization, |
274 | final boolean dataLengthIndicator, |
275 | final AbstractID3v2FrameBody body) { |
276 | super(body); |
277 | this.readOnly = readOnly; |
278 | this.groupingIdentity = groupingIdentity; |
279 | this.compression = compression; |
280 | this.encryption = encryption; |
281 | this.unsynchronization = unsynchronization; |
282 | this.dataLengthIndicator = dataLengthIndicator; |
283 | } |
284 | |
285 | /** |
286 | * Creates a new ID3v2_4Frame object. |
287 | */ |
288 | public ID3v2_4Frame(final Lyrics3v2Field field) throws InvalidTagException { |
289 | final String id = field.getIdentifier(); |
290 | final String value; |
291 | if (id.equals("IND")) { |
292 | throw new InvalidTagException("Cannot create ID3v2.40 frame from Lyrics3 indications field."); |
293 | } else if (id.equals("LYR")) { |
294 | final FieldBodyLYR lyric = (FieldBodyLYR) field.getBody(); |
295 | ObjectLyrics3Line line; |
296 | final Iterator iterator = lyric.iterator(); |
297 | final FrameBodySYLT sync; |
298 | final FrameBodyUSLT unsync; |
299 | final boolean hasTimeStamp = lyric.hasTimeStamp(); |
300 | |
301 | // we'll create only one frame here. |
302 | // if there is any timestamp at all, we will create a sync'ed frame. |
303 | sync = new FrameBodySYLT((byte) 0, "ENG", (byte) 2, (byte) 1, ""); |
304 | unsync = new FrameBodyUSLT((byte) 0, "ENG", "", ""); |
305 | while (iterator.hasNext()) { |
306 | line = (ObjectLyrics3Line) iterator.next(); |
307 | if (hasTimeStamp) { |
308 | sync.addLyric(line); |
309 | } else { |
310 | unsync.addLyric(line); |
311 | } |
312 | } |
313 | if (hasTimeStamp) { |
314 | this.setBody(sync); |
315 | } else { |
316 | this.setBody(unsync); |
317 | } |
318 | } else if (id.equals("INF")) { |
319 | value = ((FieldBodyINF) field.getBody()).getAdditionalInformation(); |
320 | this.setBody(new FrameBodyCOMM((byte) 0, "ENG", "", value)); |
321 | } else if (id.equals("AUT")) { |
322 | value = ((FieldBodyAUT) field.getBody()).getAuthor(); |
323 | this.setBody(new FrameBodyTCOM((byte) 0, value)); |
324 | } else if (id.equals("EAL")) { |
325 | value = ((FieldBodyEAL) field.getBody()).getAlbum(); |
326 | this.setBody(new FrameBodyTALB((byte) 0, value)); |
327 | } else if (id.equals("EAR")) { |
328 | value = ((FieldBodyEAR) field.getBody()).getArtist(); |
329 | this.setBody(new FrameBodyTPE1((byte) 0, value)); |
330 | } else if (id.equals("ETT")) { |
331 | value = ((FieldBodyETT) field.getBody()).getTitle(); |
332 | this.setBody(new FrameBodyTIT2((byte) 0, value)); |
333 | } else if (id.equals("IMG")) { |
334 | throw new InvalidTagException("Cannot create ID3v2.40 frame from Lyrics3 image field."); |
335 | } else { |
336 | throw new InvalidTagException("Cannot caret ID3v2.40 frame from " + id + " Lyrics3 field"); |
337 | } |
338 | } |
339 | |
340 | /** |
341 | * Creates a new ID3v2_4Frame object. |
342 | */ |
343 | public ID3v2_4Frame(final RandomAccessFile file) throws IOException, InvalidTagException { |
344 | this.read(file); |
345 | } |
346 | |
347 | public int getSize() { |
348 | return this.getBody().getSize() + 4 + 2 + 4; |
349 | } |
350 | |
351 | public boolean equals(final Object obj) { |
352 | if ((obj instanceof ID3v2_4Frame) == false) { |
353 | return false; |
354 | } |
355 | final ID3v2_4Frame id3v2_4Frame = (ID3v2_4Frame) obj; |
356 | if (this.unsynchronization != id3v2_4Frame.unsynchronization) { |
357 | return false; |
358 | } |
359 | if (this.dataLengthIndicator != id3v2_4Frame.dataLengthIndicator) { |
360 | return false; |
361 | } |
362 | return super.equals(obj); |
363 | } |
364 | |
365 | public void read(final RandomAccessFile file) throws IOException, InvalidTagException { |
366 | long filePointer; |
367 | final byte[] buffer = new byte[4]; |
368 | byte b; |
369 | |
370 | // lets scan for a non-zero byte; |
371 | do { |
372 | filePointer = file.getFilePointer(); |
373 | b = file.readByte(); |
374 | org.farng.mp3.id3.AbstractID3v2.incrementPaddingCounter(); |
375 | } while (b == 0); |
376 | file.seek(filePointer); |
377 | org.farng.mp3.id3.AbstractID3v2.decrementPaddingCounter(); |
378 | |
379 | // read the four character identifier |
380 | file.read(buffer, 0, 4); |
381 | final String identifier = new String(buffer, 0, 4); |
382 | |
383 | // is this a valid identifier? |
384 | if (isValidID3v2FrameIdentifier(identifier) == false) { |
385 | file.seek(file.getFilePointer() - 3); |
386 | throw new InvalidTagException(identifier + " is not a valid ID3v2.40 frame"); |
387 | } |
388 | filePointer = file.getFilePointer(); |
389 | |
390 | // skip the 4 byte size |
391 | file.skipBytes(4); |
392 | |
393 | // read the flag bytes |
394 | file.read(buffer, 0, 2); |
395 | this.tagAlterPreservation = (buffer[0] & TagConstant.MASK_V24_TAG_ALTER_PRESERVATION) != 0; |
396 | this.fileAlterPreservation = (buffer[0] & TagConstant.MASK_V24_FILE_ALTER_PRESERVATION) != 0; |
397 | this.readOnly = (buffer[0] & TagConstant.MASK_V24_READ_ONLY) != 0; |
398 | this.groupingIdentity = (buffer[1] & TagConstant.MASK_V24_GROUPING_IDENTITY) != 0; |
399 | this.compression = (buffer[1] & TagConstant.MASK_V24_COMPRESSION) != 0; |
400 | this.encryption = (buffer[1] & TagConstant.MASK_V24_ENCRYPTION) != 0; |
401 | this.unsynchronization = (buffer[1] & TagConstant.MASK_V24_FRAME_UNSYNCHRONIZATION) != 0; |
402 | this.dataLengthIndicator = (buffer[1] & TagConstant.MASK_V24_DATA_LENGTH_INDICATOR) != 0; |
403 | file.seek(filePointer); |
404 | this.setBody(readBody(identifier, file)); |
405 | } |
406 | |
407 | public void write(final RandomAccessFile file) throws IOException { |
408 | final byte[] buffer = new byte[4]; |
409 | final long filePointer; |
410 | final String str = TagUtility.truncate(getIdentifier(), 4); |
411 | for (int i = 0; i < str.length(); i++) { |
412 | buffer[i] = (byte) str.charAt(i); |
413 | } |
414 | file.write(buffer, 0, str.length()); |
415 | filePointer = file.getFilePointer(); |
416 | |
417 | // skip the size bytes |
418 | file.skipBytes(4); |
419 | setAlterPreservation(); |
420 | buffer[0] = 0; |
421 | buffer[1] = 0; |
422 | if (this.tagAlterPreservation) { |
423 | buffer[0] |= TagConstant.MASK_V24_TAG_ALTER_PRESERVATION; |
424 | } |
425 | if (this.fileAlterPreservation) { |
426 | buffer[0] |= TagConstant.MASK_V24_FILE_ALTER_PRESERVATION; |
427 | } |
428 | if (this.readOnly) { |
429 | buffer[0] |= TagConstant.MASK_V24_READ_ONLY; |
430 | } |
431 | if (this.groupingIdentity) { |
432 | buffer[1] |= TagConstant.MASK_V24_GROUPING_IDENTITY; |
433 | } |
434 | if (this.compression) { |
435 | buffer[1] |= TagConstant.MASK_V24_COMPRESSION; |
436 | } |
437 | if (this.encryption) { |
438 | buffer[1] |= TagConstant.MASK_V24_ENCRYPTION; |
439 | } |
440 | if (this.unsynchronization) { |
441 | buffer[1] |= TagConstant.MASK_V24_FRAME_UNSYNCHRONIZATION; |
442 | } |
443 | if (this.dataLengthIndicator) { |
444 | buffer[1] |= TagConstant.MASK_V24_DATA_LENGTH_INDICATOR; |
445 | } |
446 | file.write(buffer, 0, 2); |
447 | file.seek(filePointer); |
448 | this.getBody().write(file); |
449 | } |
450 | } |