1 | package org.farng.mp3.id3; |
2 | |
3 | import org.farng.mp3.AbstractMP3Tag; |
4 | import org.farng.mp3.InvalidTagException; |
5 | import org.farng.mp3.MP3File; |
6 | import org.farng.mp3.TagConstant; |
7 | import org.farng.mp3.TagException; |
8 | import org.farng.mp3.TagNotFoundException; |
9 | import org.farng.mp3.filename.FilenameTag; |
10 | import org.farng.mp3.lyrics3.AbstractLyrics3; |
11 | import org.farng.mp3.lyrics3.Lyrics3v2; |
12 | import org.farng.mp3.lyrics3.Lyrics3v2Field; |
13 | |
14 | import java.io.IOException; |
15 | import java.io.RandomAccessFile; |
16 | import java.util.Iterator; |
17 | |
18 | /** |
19 | * <p> ID3v2 is a general tagging format for audio, which makes it possible<br> to store meta |
20 | * data about the audio inside the audio file itself. The<br> ID3 tag described in this document is mainly |
21 | * targeted at files<br> encoded with MPEG-1/2 layer I, MPEG-1/2 layer II, MPEG-1/2 layer III<br> |
22 | * and MPEG-2.5, but may work with other types of encoded audio or as a<br> stand alone format |
23 | * for audio meta data.</p> |
24 | * <p/> |
25 | * <p> ID3v2 is designed to be as flexible and expandable as possible to<br> meet new meta |
26 | * information needs that might arise. To achieve that<br> ID3v2 is constructed as a container for several |
27 | * information blocks,<br> called frames, whose format need not be known to the software that<br> |
28 | * encounters them. At the start of every frame is an unique and<br> predefined identifier, a |
29 | * size descriptor that allows software to skip<br> unknown frames and a flags field. The flags describes |
30 | * encoding<br> details and if the frame should remain in the tag, should it be<br> unknown to |
31 | * the software, if the file is altered.</p> |
32 | * <p/> |
33 | * <p> The bitorder in ID3v2 is most significant bit first (MSB). The<br> byteorder in |
34 | * multibyte numbers is most significant byte first (e.g.<br> $12345678 would be encoded $12 34 56 78), |
35 | * also known as big endian<br> and network byte order.</p> |
36 | * <p/> |
37 | * <p> Overall tag structure:</p> <table border="1"> <tr> <td width="100%" align="center"> <p |
38 | * align="center">Header (10 bytes)</p> </td> </tr> <tr> <td width="100%" align="center">Extended Header (variable |
39 | * length, OPTIONAL)</td> </tr> <tr> <td width="100%" align="center">Frames (variable length)</td> </tr> <tr> <td |
40 | * width="100%" align="center">Padding (variable length, OPTIONAL)</td> </tr> <tr> <td width="100%" |
41 | * align="center">Footer (10 bytes, OPTIONAL)</td> </tr> </table> <p> In general, padding and footer are |
42 | * mutually exclusive. See details in<br> sections 3.3, 3.4 and 5.<br> </p> <a name="sec3.1"></a> |
43 | * <p/> |
44 | * <h3>3.1. ID3v2 header</h3> |
45 | * <p/> |
46 | * <p> The first part of the ID3v2 tag is the 10 byte tag header, laid out<br> as follows:</p> |
47 | * <p/> |
48 | * <p> ID3v2/file identifier "ID3"<br> |
49 | * ID3v2 version |
50 | * $04 00<br> ID3v2 flags |
51 | * %abcd0000<br> ID3v2 size |
52 | * 4 * %0xxxxxxx</p> |
53 | * <p/> |
54 | * <p> The first three bytes of the tag are always "ID3", to indicate that<br> this |
55 | * is an ID3v2 tag, directly followed by the two version bytes. The<br> first byte of ID3v2 version is its |
56 | * major version, while the second<br> byte is its revision number. In this case this is ID3v2.4.0. All<br> |
57 | * revisions are backwards compatible while major versions are not. If<br> software with |
58 | * ID3v2.4.0 and below support should encounter version<br> five or higher it should simply ignore the |
59 | * whole tag. Version or<br> revision will never be $FF.</p> |
60 | * <p/> |
61 | * <p> The version is followed by the ID3v2 flags field, of which currently<br> four flags are |
62 | * used.<br> </p> |
63 | * <p/> |
64 | * <p> a - Unsynchronisation</p> |
65 | * <p/> |
66 | * <p> Bit 7 in the 'ID3v2 flags' indicates whether or not<br> |
67 | * unsynchronisation is applied on all frames (see section 6.1 for<br> details); a set bit |
68 | * indicates usage.<br> </p> |
69 | * <p/> |
70 | * <p> b - Extended header</p> |
71 | * <p/> |
72 | * <p> The second bit (bit 6) indicates whether or not the header is<br> |
73 | * followed by an extended header. The extended header is described in<br> |
74 | * section 3.2. A set bit indicates the presence of an extended<br> |
75 | * header.<br> </p> |
76 | * <p/> |
77 | * <p> c - Experimental indicator</p> |
78 | * <p/> |
79 | * <p> The third bit (bit 5) is used as an 'experimental indicator'. This<br> |
80 | * flag SHALL always be set when the tag is in an experimental stage.<br> </p> |
81 | * <p/> |
82 | * <p> d - Footer present</p> |
83 | * <p/> |
84 | * <p> Bit 4 indicates that a footer (section 3.4) is present at the very<br> |
85 | * end of the tag. A set bit indicates the presence of a footer.<br> </p> |
86 | * <p/> |
87 | * <p> All the other flags MUST be cleared. If one of these undefined flags<br> are set, the |
88 | * tag might not be readable for a parser that does not<br> know the flags function.</p> |
89 | * <p/> |
90 | * <p> The ID3v2 tag size is stored as a 32 bit synchsafe integer (section<br> 6.2), making a |
91 | * total of 28 effective bits (representing up to 256MB).</p> |
92 | * <p/> |
93 | * <p> The ID3v2 tag size is the sum of the byte length of the extended<br> header, the padding |
94 | * and the frames after unsynchronisation. If a<br> footer is present this equals to ('total size' - 20) |
95 | * bytes, otherwise<br> ('total size' - 10) bytes.</p> |
96 | * <p/> |
97 | * <p> An ID3v2 tag can be detected with the following pattern:<br> $49 44 33 yy yy |
98 | * xx zz zz zz zz<br> Where yy is less than $FF, xx is the 'flags' byte and zz is less than<br> |
99 | * $80.<br> </p> <a name="sec3.2"></a> |
100 | * <p/> |
101 | * <h3>3.2. Extended header</h3> |
102 | * <p/> |
103 | * <p> The extended header contains information that can provide further<br> insight in the |
104 | * structure of the tag, but is not vital to the correct<br> parsing of the tag information; hence the |
105 | * extended header is<br> optional.</p> |
106 | * <p/> |
107 | * <p> Extended header size 4 * %0xxxxxxx<br> Number of |
108 | * flag bytes $01<br> Extended |
109 | * Flags $xx</p> |
110 | * <p/> |
111 | * <p> Where the 'Extended header size' is the size of the whole extended<br> header, stored as |
112 | * a 32 bit synchsafe integer. An extended header can<br> thus never have a size of fewer than six |
113 | * bytes.</p> |
114 | * <p/> |
115 | * <p> The extended flags field, with its size described by 'number of flag<br> bytes', is |
116 | * defined as:</p> |
117 | * <p/> |
118 | * <p> %0bcd0000</p> |
119 | * <p/> |
120 | * <p> Each flag that is set in the extended header has data attached, which<br> comes in the |
121 | * order in which the flags are encountered (i.e. the data<br> for flag 'b' comes before the data for flag |
122 | * 'c'). Unset flags cannot<br> have any attached data. All unknown flags MUST be unset and their<br> |
123 | * corresponding data removed when a tag is modified.</p> |
124 | * <p/> |
125 | * <p> Every set flag's data starts with a length byte, which contains a<br> value between 0 |
126 | * and 127 ($00 - $7f), followed by data that has the<br> field length indicated by the length byte. If a |
127 | * flag has no attached<br> data, the value $00 is used as length byte.<br> </p> |
128 | * <p/> |
129 | * <p> b - Tag is an update</p> |
130 | * <p/> |
131 | * <p> If this flag is set, the present tag is an update of a tag found<br> |
132 | * earlier in the present file or stream. If frames defined as unique<br> |
133 | * are found in the present tag, they are to override any<br> |
134 | * corresponding ones found in the earlier tag. This flag has no<br> corresponding data.</p> |
135 | * <p/> |
136 | * <p> Flag data length $00</p> |
137 | * <p/> |
138 | * <p> c - CRC data present</p> |
139 | * <p/> |
140 | * <p> If this flag is set, a CRC-32 [ISO-3309] data is included in the<br> |
141 | * extended header. The CRC is calculated on all the data between the<br> |
142 | * header and footer as indicated by the header's tag length field,<br> |
143 | * minus the extended header. Note that this includes the padding (if<br> |
144 | * there is any), but excludes the footer. The CRC-32 is stored as an<br> |
145 | * 35 bit synchsafe integer, leaving the upper four bits always<br> |
146 | * zeroed.</p> |
147 | * <p/> |
148 | * <p> Flag data length $05<br> |
149 | * Total frame CRC 5 * %0xxxxxxx</p> |
150 | * <p/> |
151 | * <p> d - Tag restrictions</p> |
152 | * <p/> |
153 | * <p> For some applications it might be desired to restrict a tag in more<br> |
154 | * ways than imposed by the ID3v2 specification. Note that the<br> |
155 | * presence of these restrictions does not affect how the tag is<br> decoded, merely how it was |
156 | * restricted before encoding. If this flag<br> is set the tag is restricted as follows:</p> |
157 | * <p/> |
158 | * <p> Flag data length $01<br> |
159 | * Restrictions |
160 | * %ppqrrstt</p> |
161 | * <p/> |
162 | * <p> p - Tag size restrictions</p> |
163 | * <p/> |
164 | * <p> 00 No more than 128 frames and 1 MB total tag size.<br> |
165 | * 01 No more than 64 frames and 128 KB total tag size.<br> |
166 | * 10 No more than 32 frames and 40 KB total tag size.<br> |
167 | * 11 No more than 32 frames and 4 KB total tag size.</p> |
168 | * <p/> |
169 | * <p> q - Text encoding restrictions</p> |
170 | * <p/> |
171 | * <p> 0 No restrictions<br> |
172 | * 1 Strings are only encoded with ISO-8859-1 [ISO-8859-1] or<br> |
173 | * UTF-8 [UTF-8].</p> |
174 | * <p/> |
175 | * <p> r - Text fields size restrictions</p> |
176 | * <p/> |
177 | * <p> 00 No restrictions<br> |
178 | * 01 No string is longer than 1024 characters.<br> 10 No |
179 | * string is longer than 128 characters.<br> 11 No string is longer |
180 | * than 30 characters.</p> |
181 | * <p/> |
182 | * <p> Note that nothing is said about how many bytes is used to<br> |
183 | * represent those characters, since it is encoding dependent. If a<br> |
184 | * text frame consists of more than one string, the sum of the<br> |
185 | * strungs is restricted as stated.</p> |
186 | * <p/> |
187 | * <p> s - Image encoding restrictions</p> |
188 | * <p/> |
189 | * <p> 0 No restrictions<br> |
190 | * 1 Images are encoded only with PNG [PNG] or JPEG [JFIF].</p> |
191 | * <p/> |
192 | * <p> t - Image size restrictions</p> |
193 | * <p/> |
194 | * <p> 00 No restrictions<br> 01 |
195 | * All images are 256x256 pixels or smaller.<br> 10 All images are 64x64 |
196 | * pixels or smaller.<br> 11 All images are exactly 64x64 pixels, unless |
197 | * required<br> otherwise.<br> </p> <a name="sec3.3"></a> |
198 | * <p/> |
199 | * <h3>3.3. Padding</h3> |
200 | * <p/> |
201 | * <p> It is OPTIONAL to include padding after the final frame (at the end<br> of the ID3 tag), |
202 | * making the size of all the frames together smaller<br> than the size given in the tag header. A possible |
203 | * purpose of this<br> padding is to allow for adding a few additional frames or enlarge<br> |
204 | * existing frames within the tag without having to rewrite the entire<br> file. The value of the padding |
205 | * bytes must be $00. A tag MUST NOT have<br> any padding between the frames or between the tag header and |
206 | * the<br> frames. Furthermore it MUST NOT have any padding when a tag footer is<br> added to |
207 | * the tag.<br> </p> <a name="sec3.4"></a> |
208 | * <p/> |
209 | * <h3>3.4. ID3v2 footer</h3> |
210 | * <p/> |
211 | * <p> To speed up the process of locating an ID3v2 tag when searching from<br> the end of a |
212 | * file, a footer can be added to the tag. It is REQUIRED<br> to add a footer to an appended tag, i.e. a |
213 | * tag located after all<br> audio data. The footer is a copy of the header, but with a different<br> |
214 | * identifier.</p> |
215 | * <p/> |
216 | * <p> ID3v2 identifier |
217 | * "3DI"<br> ID3v2 version |
218 | * $04 00<br> ID3v2 flags |
219 | * %abcd0000<br> ID3v2 size |
220 | * 4 * %0xxxxxxx<br> </p> |
221 | * <p/> |
222 | * <p> The default location of an ID3v2 tag is prepended to the audio so<br> that players can |
223 | * benefit from the information when the data is<br> streamed. It is however possible to append the tag, or |
224 | * make a<br> prepend/append combination. When deciding upon where an unembedded<br> tag |
225 | * should be located, the following order of preference SHOULD be<br> considered.<br> </p> |
226 | * <p/> |
227 | * <p> 1. Prepend the tag.</p> |
228 | * <p/> |
229 | * <p> 2. Prepend a tag with all vital information and add a second tag at <br> |
230 | * the end of the file, before tags from other tagging systems. The<br> |
231 | * first tag is required to have a SEEK frame.<br> |
232 | * </p> |
233 | * <p/> |
234 | * <p> 3. Add a tag at the end of the file, before tags from other tagging<br> |
235 | * systems.<br> </p> |
236 | * <p/> |
237 | * <p> In case 2 and 3 the tag can simply be appended if no other known tags<br> are present. |
238 | * The suggested method to find ID3v2 tags are:<br> </p> |
239 | * <p/> |
240 | * <p> 1. Look for a prepended tag using the pattern found in section 3.1.</p> |
241 | * <p/> |
242 | * <p> 2. If a SEEK frame was found, use its values to guide further<br> |
243 | * searching.</p> |
244 | * <p/> |
245 | * <p> 3. Look for a tag footer, scanning from the back of the file.</p> |
246 | * <p/> |
247 | * <p> For every new tag that is found, the old tag should be discarded<br> unless the update |
248 | * flag in the extended header (section 3.2) is set.<br> <br> </p> <a name="sec6"></a> |
249 | * <p/> |
250 | * <h3>6. Unsynchronisation</h3> |
251 | * <p/> |
252 | * <p> The only purpose of unsynchronisation is to make the ID3v2 tag as<br> compatible as |
253 | * possible with existing software and hardware. There is<br> no use in 'unsynchronising' tags if the file |
254 | * is only to be processed<br> only by ID3v2 aware software and hardware. Unsynchronisation is only<br> |
255 | * useful with tags in MPEG 1/2 layer I, II and III, MPEG 2.5 and AAC<br> files.<br> </p> |
256 | * |
257 | * @author Eric Farng |
258 | * @version $Revision: 1.6 $ |
259 | */ |
260 | public class ID3v2_4 extends ID3v2_3 { |
261 | |
262 | protected boolean footer = false; |
263 | protected boolean tagRestriction = false; |
264 | protected boolean updateTag = false; |
265 | protected byte imageEncodingRestriction = 0; |
266 | protected byte imageSizeRestriction = 0; |
267 | protected byte tagSizeRestriction = 0; |
268 | protected byte textEncodingRestriction = 0; |
269 | protected byte textFieldSizeRestriction = 0; |
270 | |
271 | /** |
272 | * Creates a new ID3v2_4 object. |
273 | */ |
274 | public ID3v2_4() { |
275 | setMajorVersion((byte) 2); |
276 | setRevision((byte) 4); |
277 | } |
278 | |
279 | /** |
280 | * Creates a new ID3v2_4 object. |
281 | */ |
282 | public ID3v2_4(final ID3v2_4 copyObject) { |
283 | super(copyObject); |
284 | this.footer = copyObject.footer; |
285 | this.tagRestriction = copyObject.tagRestriction; |
286 | this.updateTag = copyObject.updateTag; |
287 | this.imageEncodingRestriction = copyObject.imageEncodingRestriction; |
288 | this.imageSizeRestriction = copyObject.imageSizeRestriction; |
289 | this.tagSizeRestriction = copyObject.tagSizeRestriction; |
290 | this.textEncodingRestriction = copyObject.textEncodingRestriction; |
291 | this.textFieldSizeRestriction = copyObject.textFieldSizeRestriction; |
292 | } |
293 | |
294 | /** |
295 | * Creates a new ID3v2_4 object. |
296 | */ |
297 | public ID3v2_4(final AbstractMP3Tag mp3tag) { |
298 | if (mp3tag != null) { |
299 | // if we get a tag, we want to convert to id3v2_4 |
300 | // both id3v1 and lyrics3 convert to this type |
301 | // id3v1 needs to convert to id3v2_4 before converting to lyrics3 |
302 | if (mp3tag instanceof AbstractID3v2) { |
303 | copyFromID3v2Tag((AbstractID3v2) mp3tag); |
304 | } else if (mp3tag instanceof ID3v1) { |
305 | // convert id3v1 tags. |
306 | final ID3v1 id3tag = (ID3v1) mp3tag; |
307 | ID3v2_4Frame newFrame; |
308 | AbstractID3v2FrameBody newBody; |
309 | if (id3tag.title.length() > 0) { |
310 | newBody = new FrameBodyTIT2((byte) 0, id3tag.title); |
311 | newFrame = new ID3v2_4Frame(false, false, false, false, false, false, newBody); |
312 | this.setFrame(newFrame); |
313 | } |
314 | if (id3tag.artist.length() > 0) { |
315 | newBody = new FrameBodyTPE1((byte) 0, id3tag.artist); |
316 | newFrame = new ID3v2_4Frame(false, false, false, false, false, false, newBody); |
317 | this.setFrame(newFrame); |
318 | } |
319 | if (id3tag.album.length() > 0) { |
320 | newBody = new FrameBodyTALB((byte) 0, id3tag.album); |
321 | newFrame = new ID3v2_4Frame(false, false, false, false, false, false, newBody); |
322 | this.setFrame(newFrame); |
323 | } |
324 | if (id3tag.year.length() > 0) { |
325 | newBody = new FrameBodyTDRC((byte) 0, id3tag.year); |
326 | newFrame = new ID3v2_4Frame(false, false, false, false, false, false, newBody); |
327 | this.setFrame(newFrame); |
328 | } |
329 | if (id3tag.comment.length() > 0) { |
330 | newBody = new FrameBodyCOMM((byte) 0, "ENG", "", id3tag.comment); |
331 | newFrame = new ID3v2_4Frame(false, false, false, false, false, false, newBody); |
332 | this.setFrame(newFrame); |
333 | } |
334 | if (id3tag.genre >= 0) { |
335 | final String genre = "(" + |
336 | Byte.toString(id3tag.genre) + |
337 | ") " + |
338 | TagConstant.genreIdToString.get(new Long(id3tag.genre)); |
339 | newBody = new FrameBodyTCON((byte) 0, genre); |
340 | newFrame = new ID3v2_4Frame(false, false, false, false, false, false, newBody); |
341 | this.setFrame(newFrame); |
342 | } |
343 | if (mp3tag instanceof ID3v1_1) { |
344 | final ID3v1_1 id3tag2 = (ID3v1_1) mp3tag; |
345 | if (id3tag2.track > 0) { |
346 | newBody = new FrameBodyTRCK((byte) 0, Byte.toString(id3tag2.track)); |
347 | newFrame = new ID3v2_4Frame(false, false, false, false, false, false, newBody); |
348 | this.setFrame(newFrame); |
349 | } |
350 | } |
351 | } else if (mp3tag instanceof AbstractLyrics3) { |
352 | // put the conversion stuff in the individual frame code. |
353 | final Lyrics3v2 lyric; |
354 | if (mp3tag instanceof Lyrics3v2) { |
355 | lyric = new Lyrics3v2((Lyrics3v2) mp3tag); |
356 | } else { |
357 | lyric = new Lyrics3v2(mp3tag); |
358 | } |
359 | final Iterator iterator = lyric.iterator(); |
360 | Lyrics3v2Field field; |
361 | ID3v2_4Frame newFrame; |
362 | while (iterator.hasNext()) { |
363 | try { |
364 | field = (Lyrics3v2Field) iterator.next(); |
365 | newFrame = new ID3v2_4Frame(field); |
366 | this.setFrame(newFrame); |
367 | } catch (InvalidTagException ex) { |
368 | } |
369 | } |
370 | } else if (mp3tag instanceof FilenameTag) { |
371 | copyFromID3v2Tag(((FilenameTag) mp3tag).getId3tag()); |
372 | } |
373 | } |
374 | } |
375 | |
376 | /** |
377 | * Creates a new ID3v2_4 object. |
378 | */ |
379 | public ID3v2_4(final RandomAccessFile file) throws TagException, IOException { |
380 | this.read(file); |
381 | } |
382 | |
383 | public String getIdentifier() { |
384 | return "ID3v2.40"; |
385 | } |
386 | |
387 | public int getSize() { |
388 | int size = 3 + 2 + 1 + 4; |
389 | if (this.extended) { |
390 | size += (4 + 1 + 1); |
391 | if (this.updateTag) { |
392 | size++; |
393 | } |
394 | if (this.crcDataFlag) { |
395 | size += 5; |
396 | } |
397 | if (this.tagRestriction) { |
398 | size += 2; |
399 | } |
400 | } |
401 | final Iterator iterator = this.getFrameIterator(); |
402 | AbstractID3v2Frame frame; |
403 | while (iterator.hasNext()) { |
404 | frame = (AbstractID3v2Frame) iterator.next(); |
405 | size += frame.getSize(); |
406 | } |
407 | return size; |
408 | } |
409 | |
410 | public void append(final AbstractMP3Tag tag) { |
411 | if (tag instanceof ID3v2_4) { |
412 | this.updateTag = ((ID3v2_4) tag).updateTag; |
413 | this.footer = ((ID3v2_4) tag).footer; |
414 | this.tagRestriction = ((ID3v2_4) tag).tagRestriction; |
415 | this.tagSizeRestriction = ((ID3v2_4) tag).tagSizeRestriction; |
416 | this.textEncodingRestriction = ((ID3v2_4) tag).textEncodingRestriction; |
417 | this.textFieldSizeRestriction = ((ID3v2_4) tag).textFieldSizeRestriction; |
418 | this.imageEncodingRestriction = ((ID3v2_4) tag).imageEncodingRestriction; |
419 | this.imageSizeRestriction = ((ID3v2_4) tag).imageSizeRestriction; |
420 | } |
421 | super.append(tag); |
422 | } |
423 | |
424 | public boolean equals(final Object obj) { |
425 | if ((obj instanceof ID3v2_4) == false) { |
426 | return false; |
427 | } |
428 | final ID3v2_4 id3v2_4 = (ID3v2_4) obj; |
429 | if (this.footer != id3v2_4.footer) { |
430 | return false; |
431 | } |
432 | if (this.imageEncodingRestriction != id3v2_4.imageEncodingRestriction) { |
433 | return false; |
434 | } |
435 | if (this.imageSizeRestriction != id3v2_4.imageSizeRestriction) { |
436 | return false; |
437 | } |
438 | if (this.tagRestriction != id3v2_4.tagRestriction) { |
439 | return false; |
440 | } |
441 | if (this.tagSizeRestriction != id3v2_4.tagSizeRestriction) { |
442 | return false; |
443 | } |
444 | if (this.textEncodingRestriction != id3v2_4.textEncodingRestriction) { |
445 | return false; |
446 | } |
447 | if (this.textFieldSizeRestriction != id3v2_4.textFieldSizeRestriction) { |
448 | return false; |
449 | } |
450 | if (this.updateTag != id3v2_4.updateTag) { |
451 | return false; |
452 | } |
453 | return super.equals(obj); |
454 | } |
455 | |
456 | public void overwrite(final AbstractMP3Tag tag) { |
457 | if (tag instanceof ID3v2_4) { |
458 | this.updateTag = ((ID3v2_4) tag).updateTag; |
459 | this.footer = ((ID3v2_4) tag).footer; |
460 | this.tagRestriction = ((ID3v2_4) tag).tagRestriction; |
461 | this.tagSizeRestriction = ((ID3v2_4) tag).tagSizeRestriction; |
462 | this.textEncodingRestriction = ((ID3v2_4) tag).textEncodingRestriction; |
463 | this.textFieldSizeRestriction = ((ID3v2_4) tag).textFieldSizeRestriction; |
464 | this.imageEncodingRestriction = ((ID3v2_4) tag).imageEncodingRestriction; |
465 | this.imageSizeRestriction = ((ID3v2_4) tag).imageSizeRestriction; |
466 | } |
467 | super.overwrite(tag); |
468 | } |
469 | |
470 | public void read(final RandomAccessFile file) throws TagException, IOException { |
471 | final int size; |
472 | byte[] buffer = new byte[4]; |
473 | file.seek(0); |
474 | if (seek(file) == false) { |
475 | throw new TagNotFoundException(getIdentifier() + " tag not found"); |
476 | } |
477 | |
478 | // read the major and minor @version bytes & flag bytes |
479 | file.read(buffer, 0, 3); |
480 | if ((buffer[0] != 4) || (buffer[1] != 0)) { |
481 | throw new TagNotFoundException(getIdentifier() + " tag not found"); |
482 | } |
483 | setMajorVersion(buffer[0]); |
484 | setRevision(buffer[1]); |
485 | this.unsynchronization = (buffer[2] & TagConstant.MASK_V24_UNSYNCHRONIZATION) != 0; |
486 | this.extended = (buffer[2] & TagConstant.MASK_V24_EXTENDED_HEADER) != 0; |
487 | this.experimental = (buffer[2] & TagConstant.MASK_V24_EXPERIMENTAL) != 0; |
488 | this.footer = (buffer[2] & TagConstant.MASK_V24_FOOTER_PRESENT) != 0; |
489 | |
490 | // read the size |
491 | file.read(buffer, 0, 4); |
492 | size = byteArrayToSize(buffer); |
493 | final long filePointer = file.getFilePointer(); |
494 | if (this.extended) { |
495 | // int is 4 bytes. |
496 | final int extendedHeaderSize = file.readInt(); |
497 | |
498 | // the extended header must be atleast 6 bytes |
499 | if (extendedHeaderSize <= 6) { |
500 | throw new InvalidTagException("Invalid Extended Header Size."); |
501 | } |
502 | final byte numberOfFlagBytes = file.readByte(); |
503 | |
504 | // read the flag bytes |
505 | file.read(buffer, 0, numberOfFlagBytes); |
506 | this.updateTag = (buffer[0] & TagConstant.MASK_V24_TAG_UPDATE) != 0; |
507 | this.crcDataFlag = (buffer[0] & TagConstant.MASK_V24_CRC_DATA_PRESENT) != 0; |
508 | this.tagRestriction = (buffer[0] & TagConstant.MASK_V24_TAG_RESTRICTIONS) != 0; |
509 | |
510 | // read the length byte if the flag is set |
511 | // this tag should always be zero but just in case |
512 | // read this information. |
513 | if (this.updateTag) { |
514 | final int len = file.readByte(); |
515 | buffer = new byte[len]; |
516 | file.read(buffer, 0, len); |
517 | } |
518 | if (this.crcDataFlag) { |
519 | // the CRC has a variable length |
520 | final int len = file.readByte(); |
521 | buffer = new byte[len]; |
522 | file.read(buffer, 0, len); |
523 | this.crcData = 0; |
524 | for (int i = 0; i < len; i++) { |
525 | this.crcData <<= 8; |
526 | this.crcData += buffer[i]; |
527 | } |
528 | } |
529 | if (this.tagRestriction) { |
530 | final int len = file.readByte(); |
531 | buffer = new byte[len]; |
532 | file.read(buffer, 0, len); |
533 | this.tagSizeRestriction = (byte) ((buffer[0] & TagConstant.MASK_V24_TAG_SIZE_RESTRICTIONS) >> 6); |
534 | this.textEncodingRestriction = (byte) ((buffer[0] & TagConstant.MASK_V24_TEXT_ENCODING_RESTRICTIONS) >> |
535 | 5); |
536 | this.textFieldSizeRestriction = (byte) ((buffer[0] & TagConstant |
537 | .MASK_V24_TEXT_FIELD_SIZE_RESTRICTIONS) >> 3); |
538 | this.imageEncodingRestriction = (byte) ((buffer[0] & TagConstant.MASK_V24_IMAGE_ENCODING) >> 2); |
539 | this.imageSizeRestriction = (byte) (buffer[0] & TagConstant.MASK_V24_IMAGE_SIZE_RESTRICTIONS); |
540 | } |
541 | } |
542 | ID3v2_4Frame next; |
543 | this.clearFrameMap(); |
544 | |
545 | // read the frames |
546 | this.setFileReadBytes(size); |
547 | resetPaddingCounter(); |
548 | while ((file.getFilePointer() - filePointer) <= size) { |
549 | try { |
550 | next = new ID3v2_4Frame(file); |
551 | final String id = next.getIdentifier(); |
552 | if (this.hasFrame(id)) { |
553 | this.appendDuplicateFrameId(id + "; "); |
554 | this.incrementDuplicateBytes(this.getFrame(id).getSize()); |
555 | } |
556 | this.setFrame(next); |
557 | } catch (InvalidTagException ex) { |
558 | if (ex.getMessage().equals("Found empty frame")) { |
559 | this.incrementEmptyFrameBytes(10); |
560 | } else { |
561 | this.incrementInvalidFrameBytes(); |
562 | } |
563 | } |
564 | } |
565 | this.setPaddingSize(getPaddingCounter()); |
566 | |
567 | /** |
568 | * int newSize = this.getSize(); if ((this.padding + newSize - 10) != |
569 | * size) { System.out.println("WARNING: Tag sizes don't add up"); |
570 | * System.out.println("ID3v2.40 tag size : " + newSize); |
571 | * System.out.println("ID3v2.40 padding : " + this.padding); |
572 | * System.out.println("ID3v2.40 total : " + (this.padding + newSize)); |
573 | * System.out.println("ID3v2.40 file size: " + size); } |
574 | */ |
575 | } |
576 | |
577 | public boolean seek(final RandomAccessFile file) throws IOException { |
578 | final byte[] buffer = new byte[3]; |
579 | file.seek(0); |
580 | |
581 | // read the tag if it exists |
582 | file.read(buffer, 0, 3); |
583 | final String tag = new String(buffer, 0, 3); |
584 | if (tag.equals("ID3") == false) { |
585 | return false; |
586 | } |
587 | |
588 | // read the major and minor @version number |
589 | file.read(buffer, 0, 2); |
590 | |
591 | // read back the @version bytes so we can read and save them later |
592 | file.seek(file.getFilePointer() - 2); |
593 | return ((buffer[0] == 4) && (buffer[1] == 0)); |
594 | } |
595 | |
596 | public String toString() { |
597 | final Iterator iterator = this.getFrameIterator(); |
598 | AbstractID3v2Frame frame; |
599 | String str = getIdentifier() + " " + this.getSize() + "\n"; |
600 | str += ("compression = " + this.compression + "\n"); |
601 | str += ("unsynchronization = " + this.unsynchronization + "\n"); |
602 | str += ("crcData = " + this.crcData + "\n"); |
603 | str += ("crcDataFlag = " + this.crcDataFlag + "\n"); |
604 | str += ("experimental = " + this.experimental + "\n"); |
605 | str += ("extended = " + this.extended + "\n"); |
606 | str += ("paddingSize = " + this.paddingSize + "\n"); |
607 | str += ("footer = " + this.footer + "\n"); |
608 | str += ("imageEncodingRestriction = " + this.imageEncodingRestriction + "\n"); |
609 | str += ("imageSizeRestriction = " + this.imageSizeRestriction + "\n"); |
610 | str += ("tagRestriction = " + this.tagRestriction + "\n"); |
611 | str += ("tagSizeRestriction = " + this.tagSizeRestriction + "\n"); |
612 | str += ("textEncodingRestriction = " + this.textEncodingRestriction + "\n"); |
613 | str += ("textFieldSizeRestriction = " + this.textFieldSizeRestriction + "\n"); |
614 | str += ("updateTag = " + this.updateTag + "\n"); |
615 | while (iterator.hasNext()) { |
616 | frame = (ID3v2_4Frame) iterator.next(); |
617 | str += (frame.toString() + "\n"); |
618 | } |
619 | return str + "\n"; |
620 | } |
621 | |
622 | public void write(final AbstractMP3Tag tag) { |
623 | if (tag instanceof ID3v2_4) { |
624 | this.updateTag = ((ID3v2_4) tag).updateTag; |
625 | this.footer = ((ID3v2_4) tag).footer; |
626 | this.tagRestriction = ((ID3v2_4) tag).tagRestriction; |
627 | this.tagSizeRestriction = ((ID3v2_4) tag).tagSizeRestriction; |
628 | this.textEncodingRestriction = ((ID3v2_4) tag).textEncodingRestriction; |
629 | this.textFieldSizeRestriction = ((ID3v2_4) tag).textFieldSizeRestriction; |
630 | this.imageEncodingRestriction = ((ID3v2_4) tag).imageEncodingRestriction; |
631 | this.imageSizeRestriction = ((ID3v2_4) tag).imageSizeRestriction; |
632 | } |
633 | super.write(tag); |
634 | } |
635 | |
636 | public void write(final RandomAccessFile file) throws IOException { |
637 | int size; |
638 | final String str; |
639 | final Iterator iterator; |
640 | ID3v2_4Frame frame; |
641 | final byte[] buffer = new byte[6]; |
642 | final MP3File mp3 = new MP3File(); |
643 | mp3.seekMP3Frame(file); |
644 | final long mp3start = file.getFilePointer(); |
645 | file.seek(0); |
646 | str = "ID3"; |
647 | for (int i = 0; i < str.length(); i++) { |
648 | buffer[i] = (byte) str.charAt(i); |
649 | } |
650 | buffer[3] = 4; |
651 | buffer[4] = 0; |
652 | if (this.unsynchronization) { |
653 | buffer[5] |= TagConstant.MASK_V24_UNSYNCHRONIZATION; |
654 | } |
655 | if (this.extended) { |
656 | buffer[5] |= TagConstant.MASK_V24_EXTENDED_HEADER; |
657 | } |
658 | if (this.experimental) { |
659 | buffer[5] |= TagConstant.MASK_V24_EXPERIMENTAL; |
660 | } |
661 | if (this.footer) { |
662 | buffer[5] |= TagConstant.MASK_V24_FOOTER_PRESENT; |
663 | } |
664 | file.write(buffer); |
665 | |
666 | // write size |
667 | file.write(sizeToByteArray((int) mp3start - 10)); |
668 | if (this.extended) { |
669 | size = 6; |
670 | if (this.updateTag) { |
671 | size++; |
672 | } |
673 | if (this.crcDataFlag) { |
674 | size += 5; |
675 | } |
676 | if (this.tagRestriction) { |
677 | size += 2; |
678 | } |
679 | file.writeInt(size); |
680 | file.writeByte(1); // always 1 byte of flags in this tag |
681 | buffer[0] = 0; |
682 | if (this.updateTag) { |
683 | buffer[0] |= TagConstant.MASK_V24_TAG_UPDATE; |
684 | } |
685 | if (this.crcDataFlag) { |
686 | buffer[0] |= TagConstant.MASK_V24_CRC_DATA_PRESENT; |
687 | } |
688 | if (this.tagRestriction) { |
689 | buffer[0] |= TagConstant.MASK_V24_TAG_RESTRICTIONS; |
690 | } |
691 | file.writeByte(buffer[0]); |
692 | if (this.updateTag) { |
693 | file.writeByte(0); |
694 | } |
695 | |
696 | // this can be variable length, but this is easier |
697 | if (this.crcDataFlag) { |
698 | file.writeByte(4); |
699 | file.writeInt(this.crcData); |
700 | } |
701 | if (this.tagRestriction) { |
702 | // todo we need to finish this |
703 | file.writeByte(1); |
704 | buffer[0] = (byte) 0; |
705 | if (this.tagRestriction) { |
706 | buffer[0] |= TagConstant.MASK_V24_TAG_SIZE_RESTRICTIONS; |
707 | } |
708 | file.writeByte(this.tagSizeRestriction); |
709 | file.writeByte(this.textEncodingRestriction); |
710 | file.writeByte(this.textFieldSizeRestriction); |
711 | file.writeByte(this.imageEncodingRestriction); |
712 | file.writeByte(this.imageSizeRestriction); |
713 | file.writeByte(buffer[0]); |
714 | } |
715 | } |
716 | |
717 | // write all frames |
718 | iterator = this.getFrameIterator(); |
719 | while (iterator.hasNext()) { |
720 | frame = (ID3v2_4Frame) iterator.next(); |
721 | frame.write(file); |
722 | } |
723 | } |
724 | |
725 | private void copyFromID3v2Tag(final AbstractID3v2 mp3tag) { |
726 | // if the tag is id3v2_4 |
727 | if (mp3tag instanceof ID3v2_4) { |
728 | final ID3v2_4 tag = (ID3v2_4) mp3tag; |
729 | this.footer = tag.footer; |
730 | this.tagRestriction = tag.tagRestriction; |
731 | this.updateTag = tag.updateTag; |
732 | this.imageEncodingRestriction = tag.imageEncodingRestriction; |
733 | this.imageSizeRestriction = tag.imageSizeRestriction; |
734 | this.tagSizeRestriction = tag.tagSizeRestriction; |
735 | this.textEncodingRestriction = tag.textEncodingRestriction; |
736 | this.textFieldSizeRestriction = tag.textFieldSizeRestriction; |
737 | } |
738 | if (mp3tag instanceof ID3v2_3) { |
739 | // and id3v2_4 tag is an instance of id3v2_3 also ... |
740 | final ID3v2_3 id3tag = (ID3v2_3) mp3tag; |
741 | this.extended = id3tag.extended; |
742 | this.experimental = id3tag.experimental; |
743 | this.crcDataFlag = id3tag.crcDataFlag; |
744 | this.crcData = id3tag.crcData; |
745 | this.paddingSize = id3tag.paddingSize; |
746 | } |
747 | if (mp3tag instanceof ID3v2_2) { |
748 | final ID3v2_2 id3tag = (ID3v2_2) mp3tag; |
749 | this.compression = id3tag.compression; |
750 | this.unsynchronization = id3tag.unsynchronization; |
751 | } |
752 | final AbstractID3v2 id3tag = mp3tag; |
753 | final Iterator iterator = id3tag.getFrameIterator(); |
754 | AbstractID3v2Frame frame; |
755 | ID3v2_4Frame newFrame; |
756 | while (iterator.hasNext()) { |
757 | frame = (AbstractID3v2Frame) iterator.next(); |
758 | newFrame = new ID3v2_4Frame(frame); |
759 | this.setFrame(newFrame); |
760 | } |
761 | } |
762 | |
763 | public String getYearReleased() { |
764 | String text = ""; |
765 | AbstractID3v2Frame frame = getFrame("TDRC"); |
766 | if (frame != null) { |
767 | FrameBodyTDRC body = (FrameBodyTDRC) frame.getBody(); |
768 | text = body.getText(); |
769 | } |
770 | return text.trim(); |
771 | } |
772 | |
773 | public void setYearReleased(String yearReleased) { |
774 | AbstractID3v2Frame field = getFrame("TDRC"); |
775 | if (field == null) { |
776 | field = new ID3v2_3Frame(new FrameBodyTDRC((byte) 0, yearReleased)); |
777 | setFrame(field); |
778 | } else { |
779 | ((FrameBodyTDRC) field.getBody()).setText(yearReleased); |
780 | } |
781 | } |
782 | } |