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 | |
10 | import java.io.IOException; |
11 | import java.io.RandomAccessFile; |
12 | import java.util.Iterator; |
13 | |
14 | /** |
15 | * <p class=t> The two biggest design goals were to be able to implement ID3v2 without disturbing old software too much |
16 | * and that ID3v2 should be as flexible and expandable as possible. </p> |
17 | * <p/> |
18 | * <p class=t> The first criterion is met by the simple fact that the <a href="#MPEG">MPEG</a> decoding software uses a |
19 | * syncsignal, embedded in the audiostream, to 'lock on to' the audio. Since the ID3v2 tag doesn't contain a valid |
20 | * syncsignal, no software will attempt to play the tag. If, for any reason, coincidence make a syncsignal appear within |
21 | * the tag it will be taken care of by the 'unsynchronisation scheme' described in <a href="#sec5">section 5</a>. </p> |
22 | * <p/> |
23 | * <p class=t> The second criterion has made a more noticeable impact on the design of the ID3v2 tag. It is constructed |
24 | * as a container for several information blocks, called frames, whose format need not be known to the software that |
25 | * encounters them. At the start of every frame there is an identifier that explains the frames' format and content, and |
26 | * a size descriptor that allows software to skip unknown frames. </p> |
27 | * <p/> |
28 | * <p class=t> If a total revision of the ID3v2 tag should be needed, there is a version number and a size descriptor in |
29 | * the ID3v2 header. </p> |
30 | * <p/> |
31 | * <p class=t> The ID3 tag described in this document is mainly targeted at files encoded with <a |
32 | * href="#MPEG">MPEG</a>-1/2 layer I, <a href="#MPEG">MPEG</a>-1/2 layer II, <a href="#MPEG">MPEG</a>-1/2 layer III and |
33 | * MPEG-2.5, but may work with other types of encoded audio. </p> |
34 | * <p/> |
35 | * <p class=t> The bitorder in ID3v2 is most significant bit first (MSB). The byteorder in multibyte numbers is most |
36 | * significant byte first (e.g. $12345678 would be encoded $12 34 56 78). </p> |
37 | * <p/> |
38 | * <p class=t> It is permitted to include padding after all the final frame (at the end of the ID3 tag), making the size |
39 | * of all the frames together smaller than the size given in the head of the tag. A possible purpose of this padding is |
40 | * to allow for adding a few additional frames or enlarge existing frames within the tag without having to rewrite the |
41 | * entire file. The value of the padding bytes must be $00. </p> <p class=t> The ID3v2 tag header, which should be the |
42 | * first information in the file, is 10 bytes as follows: </p> |
43 | * <p/> |
44 | * <p><center> <table border=0> <tr><td nowrap>ID3v2/file identifier</td><td rowspan=4> </td><td |
45 | * width="100%">"ID3"</td></tr> <tr><td>ID3v2 version</td><td>$03 00</td></tr> <tr><td>ID3v2 |
46 | * flags</td><td>%abc00000</td></tr> <tr><td>ID3v2 size</td><td>4 * %0xxxxxxx</td></tr> </table> </center> |
47 | * <p/> |
48 | * <p class=t> The first three bytes of the tag are always "ID3" to indicate that this is an ID3v2 tag, directly |
49 | * followed by the two version bytes. The first byte of ID3v2 version is it's major version, while the second byte is |
50 | * its revision number. In this case this is ID3v2.3.0. All revisions are backwards compatible while major versions are |
51 | * not. If software with ID3v2.2.0 and below support should encounter version three or higher it should simply ignore |
52 | * the whole tag. Version and revision will never be $FF. </p> |
53 | * <p/> |
54 | * <p class=t> The version is followed by one the ID3v2 flags field, of which currently only three flags are used. </p> |
55 | * <p/> |
56 | * <p class=t> a - Unsynchronisation </p> |
57 | * <p/> |
58 | * <p class=ind> Bit 7 in the 'ID3v2 flags' indicates whether or not unsynchronisation is used (see <a |
59 | * href="#sec5">section 5</a> for details); a set bit indicates usage.</p> |
60 | * <p/> |
61 | * <p class=t> b - Extended header </p> |
62 | * <p/> |
63 | * <p class=ind> The second bit (bit 6) indicates whether or not the header is followed by an extended header. The |
64 | * extended header is described in <a href="#sec3.2">section 3.2</a>. </p> |
65 | * <p/> |
66 | * <p class=t> c - Experimental indicator </p> |
67 | * <p/> |
68 | * <p class=ind> The third bit (bit 5) should be used as an 'experimental indicator'. This flag should always be set |
69 | * when the tag is in an experimental stage. </p> |
70 | * <p/> |
71 | * <p class=t> All the other flags should be cleared. If one of these undefined flags are set that might mean that the |
72 | * tag is not readable for a parser that does not know the flags function. </p> |
73 | * <p/> |
74 | * <p class=t> The ID3v2 tag size is encoded with four bytes where the most significant bit (bit 7) is set to zero in |
75 | * every byte, making a total of 28 bits. The zeroed bits are ignored, so a 257 bytes long tag is represented as $00 00 |
76 | * 02 01. </p> |
77 | * <p/> |
78 | * <p class=t> The ID3v2 tag size is the size of the complete tag after unsychronisation, including padding, excluding |
79 | * the header but not excluding the extended header (total tag size - 10). Only 28 bits (representing up to 256MB) are |
80 | * used in the size description to avoid the introducuction of 'false syncsignals'. </p> |
81 | * <p/> |
82 | * <p class=t> An ID3v2 tag can be detected with the following pattern:<br> $49 44 33 yy yy xx zz zz zz zz<br> Where yy |
83 | * is less than $FF, xx is the 'flags' byte and zz is less than $80. </p> |
84 | * |
85 | * @author Eric Farng |
86 | * @version $Revision: 1.5 $ |
87 | */ |
88 | public class ID3v2_3 extends ID3v2_2 { |
89 | |
90 | protected boolean crcDataFlag = false; |
91 | protected boolean experimental = false; |
92 | protected boolean extended = false; |
93 | protected int crcData = 0; |
94 | protected int paddingSize = 0; |
95 | |
96 | /** |
97 | * Creates a new ID3v2_3 object. |
98 | */ |
99 | public ID3v2_3() { |
100 | setMajorVersion((byte) 2); |
101 | setRevision((byte) 3); |
102 | } |
103 | |
104 | /** |
105 | * Creates a new ID3v2_3 object. |
106 | */ |
107 | public ID3v2_3(final ID3v2_3 copyObject) { |
108 | super(copyObject); |
109 | this.crcDataFlag = copyObject.crcDataFlag; |
110 | this.experimental = copyObject.experimental; |
111 | this.extended = copyObject.extended; |
112 | this.crcData = copyObject.crcData; |
113 | this.paddingSize = copyObject.paddingSize; |
114 | } |
115 | |
116 | /** |
117 | * Creates a new ID3v2_3 object. |
118 | */ |
119 | public ID3v2_3(final AbstractMP3Tag mp3tag) { |
120 | if (mp3tag != null) { |
121 | final ID3v2_4 convertedTag; |
122 | if ((mp3tag instanceof ID3v2_4 == false) && (mp3tag instanceof ID3v2_3 == true)) { |
123 | throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument"); |
124 | } |
125 | if (mp3tag instanceof ID3v2_4) { |
126 | convertedTag = (ID3v2_4) mp3tag; |
127 | } else { |
128 | convertedTag = new ID3v2_4(mp3tag); |
129 | } |
130 | this.extended = convertedTag.extended; |
131 | this.experimental = convertedTag.experimental; |
132 | this.crcDataFlag = convertedTag.crcDataFlag; |
133 | this.crcData = convertedTag.crcData; |
134 | this.paddingSize = convertedTag.paddingSize; |
135 | this.compression = convertedTag.compression; |
136 | this.unsynchronization = convertedTag.unsynchronization; |
137 | final AbstractID3v2 id3tag = convertedTag; |
138 | final Iterator iterator = id3tag.getFrameIterator(); |
139 | AbstractID3v2Frame frame; |
140 | ID3v2_3Frame newFrame; |
141 | while (iterator.hasNext()) { |
142 | frame = (AbstractID3v2Frame) iterator.next(); |
143 | newFrame = new ID3v2_3Frame(frame); |
144 | this.setFrame(newFrame); |
145 | } |
146 | } |
147 | } |
148 | |
149 | /** |
150 | * Creates a new ID3v2_3 object. |
151 | */ |
152 | public ID3v2_3(final RandomAccessFile file) throws TagException, IOException { |
153 | this.read(file); |
154 | } |
155 | |
156 | public String getIdentifier() { |
157 | return "ID3v2.30"; |
158 | } |
159 | |
160 | public int getSize() { |
161 | int size = 3 + 2 + 1 + 4; |
162 | if (this.extended) { |
163 | if (this.crcDataFlag) { |
164 | size += (4 + 2 + 4 + 4); |
165 | } else { |
166 | size += (4 + 2 + 4); |
167 | } |
168 | } |
169 | final Iterator iterator = this.getFrameIterator(); |
170 | AbstractID3v2Frame frame; |
171 | while (iterator.hasNext()) { |
172 | frame = (AbstractID3v2Frame) iterator.next(); |
173 | size += frame.getSize(); |
174 | } |
175 | return size; |
176 | } |
177 | |
178 | public void append(final AbstractMP3Tag tag) { |
179 | if (tag instanceof ID3v2_3) { |
180 | this.experimental = ((ID3v2_3) tag).experimental; |
181 | this.extended = ((ID3v2_3) tag).extended; |
182 | this.crcDataFlag = ((ID3v2_3) tag).crcDataFlag; |
183 | this.paddingSize = ((ID3v2_3) tag).paddingSize; |
184 | this.crcData = ((ID3v2_3) tag).crcData; |
185 | } |
186 | super.append(tag); |
187 | } |
188 | |
189 | public boolean equals(final Object obj) { |
190 | if ((obj instanceof ID3v2_3) == false) { |
191 | return false; |
192 | } |
193 | final ID3v2_3 id3v2_3 = (ID3v2_3) obj; |
194 | if (this.crcData != id3v2_3.crcData) { |
195 | return false; |
196 | } |
197 | if (this.crcDataFlag != id3v2_3.crcDataFlag) { |
198 | return false; |
199 | } |
200 | if (this.experimental != id3v2_3.experimental) { |
201 | return false; |
202 | } |
203 | if (this.extended != id3v2_3.extended) { |
204 | return false; |
205 | } |
206 | if (this.paddingSize != id3v2_3.paddingSize) { |
207 | return false; |
208 | } |
209 | return super.equals(obj); |
210 | } |
211 | |
212 | public void overwrite(final AbstractMP3Tag tag) { |
213 | if (tag instanceof ID3v2_3) { |
214 | this.experimental = ((ID3v2_3) tag).experimental; |
215 | this.extended = ((ID3v2_3) tag).extended; |
216 | this.crcDataFlag = ((ID3v2_3) tag).crcDataFlag; |
217 | this.paddingSize = ((ID3v2_3) tag).paddingSize; |
218 | this.crcData = ((ID3v2_3) tag).crcData; |
219 | } |
220 | super.overwrite(tag); |
221 | } |
222 | |
223 | public void read(final RandomAccessFile file) throws TagException, IOException { |
224 | final int size; |
225 | final byte[] buffer = new byte[4]; |
226 | if (seek(file) == false) { |
227 | throw new TagNotFoundException(getIdentifier() + " tag not found"); |
228 | } |
229 | |
230 | // read the major and minor @version number & flags byte |
231 | file.read(buffer, 0, 3); |
232 | if ((buffer[0] != 3) || (buffer[1] != 0)) { |
233 | throw new TagNotFoundException(getIdentifier() + " tag not found"); |
234 | } |
235 | setMajorVersion(buffer[0]); |
236 | setRevision(buffer[1]); |
237 | this.unsynchronization = (buffer[2] & TagConstant.MASK_V23_UNSYNCHRONIZATION) != 0; |
238 | this.extended = (buffer[2] & TagConstant.MASK_V23_EXTENDED_HEADER) != 0; |
239 | this.experimental = (buffer[2] & TagConstant.MASK_V23_EXPERIMENTAL) != 0; |
240 | |
241 | // read the size |
242 | file.read(buffer, 0, 4); |
243 | size = byteArrayToSize(buffer); |
244 | final long filePointer = file.getFilePointer(); |
245 | if (this.extended) { |
246 | // int is 4 bytes. |
247 | final int extendedHeaderSize = file.readInt(); |
248 | |
249 | // the extended header is only 6 or 10 bytes. |
250 | if (extendedHeaderSize != 6 && extendedHeaderSize != 10) { |
251 | throw new InvalidTagException("Invalid Extended Header Size."); |
252 | } |
253 | file.read(buffer, 0, 2); |
254 | this.crcDataFlag = (buffer[0] & TagConstant.MASK_V23_CRC_DATA_PRESENT) != 0; |
255 | |
256 | // if it's 10 bytes, the CRC flag must be set |
257 | // and if it's 6 bytes, it must not be set |
258 | if (((extendedHeaderSize == 10) && (this.crcDataFlag == false)) || |
259 | ((extendedHeaderSize == 6) && (this.crcDataFlag == true))) { |
260 | throw new InvalidTagException("CRC Data flag not set correctly."); |
261 | } |
262 | this.paddingSize = file.readInt(); |
263 | if ((extendedHeaderSize == 10) && this.crcDataFlag) { |
264 | this.crcData = file.readInt(); |
265 | } |
266 | } |
267 | ID3v2_3Frame next; |
268 | this.clearFrameMap(); |
269 | |
270 | // read all the frames. |
271 | this.setFileReadBytes(size); |
272 | AbstractID3v2.resetPaddingCounter(); |
273 | while ((file.getFilePointer() - filePointer) <= size) { |
274 | try { |
275 | next = new ID3v2_3Frame(file); |
276 | final String id = next.getIdentifier(); |
277 | if (this.hasFrame(id)) { |
278 | this.appendDuplicateFrameId(id + "; "); |
279 | this.incrementDuplicateBytes(this.getFrame(id).getSize()); |
280 | } |
281 | this.setFrame(next); |
282 | } catch (InvalidTagException ex) { |
283 | if (ex.getMessage().equals("Found empty frame")) { |
284 | this.incrementEmptyFrameBytes(10); |
285 | } else { |
286 | this.incrementInvalidFrameBytes(); |
287 | } |
288 | } |
289 | } |
290 | this.setPaddingSize(getPaddingCounter()); |
291 | } |
292 | |
293 | public boolean seek(final RandomAccessFile file) throws IOException { |
294 | final byte[] buffer = new byte[3]; |
295 | file.seek(0); |
296 | |
297 | // read the tag if it exists |
298 | file.read(buffer, 0, 3); |
299 | final String tag = new String(buffer, 0, 3); |
300 | if (tag.equals("ID3") == false) { |
301 | return false; |
302 | } |
303 | |
304 | // read the major and minor @version number |
305 | file.read(buffer, 0, 2); |
306 | |
307 | // read back the @version bytes so we can read and save them later |
308 | file.seek(file.getFilePointer() - 2); |
309 | return ((buffer[0] == 3) && (buffer[1] == 0)); |
310 | } |
311 | |
312 | public String toString() { |
313 | final Iterator iterator = this.getFrameIterator(); |
314 | AbstractID3v2Frame frame; |
315 | String str = getIdentifier() + " " + this.getSize() + "\n"; |
316 | str += ("compression = " + this.compression + "\n"); |
317 | str += ("unsynchronization = " + this.unsynchronization + "\n"); |
318 | str += ("crcData = " + this.crcData + "\n"); |
319 | str += ("crcDataFlag = " + this.crcDataFlag + "\n"); |
320 | str += ("experimental = " + this.experimental + "\n"); |
321 | str += ("extended = " + this.extended + "\n"); |
322 | str += ("paddingSize = " + this.paddingSize + "\n"); |
323 | while (iterator.hasNext()) { |
324 | frame = (ID3v2_3Frame) iterator.next(); |
325 | str += (frame.toString() + "\n"); |
326 | } |
327 | return str + "\n"; |
328 | } |
329 | |
330 | public void write(final AbstractMP3Tag tag) { |
331 | if (tag instanceof ID3v2_3) { |
332 | this.experimental = ((ID3v2_3) tag).experimental; |
333 | this.extended = ((ID3v2_3) tag).extended; |
334 | this.crcDataFlag = ((ID3v2_3) tag).crcDataFlag; |
335 | this.paddingSize = ((ID3v2_3) tag).paddingSize; |
336 | this.crcData = ((ID3v2_3) tag).crcData; |
337 | } |
338 | super.write(tag); |
339 | } |
340 | |
341 | public void write(final RandomAccessFile file) throws IOException { |
342 | final String str; |
343 | final Iterator iterator; |
344 | final byte[] buffer = new byte[6]; |
345 | final MP3File mp3 = new MP3File(); |
346 | mp3.seekMP3Frame(file); |
347 | final long mp3start = file.getFilePointer(); |
348 | file.seek(0); |
349 | ID3v2_3Frame frame; |
350 | str = "ID3"; |
351 | for (int i = 0; i < str.length(); i++) { |
352 | buffer[i] = (byte) str.charAt(i); |
353 | } |
354 | buffer[3] = 3; |
355 | buffer[4] = 0; |
356 | if (this.unsynchronization) { |
357 | buffer[5] |= TagConstant.MASK_V23_UNSYNCHRONIZATION; |
358 | } |
359 | if (this.extended) { |
360 | buffer[5] |= TagConstant.MASK_V23_EXTENDED_HEADER; |
361 | } |
362 | if (this.experimental) { |
363 | buffer[5] |= TagConstant.MASK_V23_EXPERIMENTAL; |
364 | } |
365 | file.write(buffer); |
366 | |
367 | // write size |
368 | file.write(sizeToByteArray((int) mp3start - 10)); |
369 | if (this.extended) { |
370 | if (this.crcDataFlag) { |
371 | file.writeInt(10); |
372 | buffer[0] = 0; |
373 | buffer[0] |= TagConstant.MASK_V23_CRC_DATA_PRESENT; |
374 | file.write(buffer, 0, 2); |
375 | file.writeInt(this.paddingSize); |
376 | file.writeInt(this.crcData); |
377 | } else { |
378 | file.writeInt(6); |
379 | file.write(buffer, 0, 2); |
380 | file.writeInt(this.paddingSize); |
381 | } |
382 | } |
383 | |
384 | // write all frames |
385 | iterator = this.getFrameIterator(); |
386 | while (iterator.hasNext()) { |
387 | frame = (ID3v2_3Frame) iterator.next(); |
388 | frame.write(file); |
389 | } |
390 | } |
391 | |
392 | public String getSongTitle() { |
393 | String text = ""; |
394 | AbstractID3v2Frame frame = getFrame("TIT2"); |
395 | if (frame != null) { |
396 | FrameBodyTIT2 body = (FrameBodyTIT2) frame.getBody(); |
397 | text = body.getText(); |
398 | } |
399 | return text.trim(); |
400 | } |
401 | |
402 | public String getLeadArtist() { |
403 | String text = ""; |
404 | AbstractID3v2Frame frame = getFrame("TPE1"); |
405 | if (frame != null) { |
406 | FrameBodyTPE1 body = (FrameBodyTPE1) frame.getBody(); |
407 | text = body.getText(); |
408 | } |
409 | return text.trim(); |
410 | } |
411 | |
412 | public String getAlbumTitle() { |
413 | String text = ""; |
414 | AbstractID3v2Frame frame = getFrame("TALB"); |
415 | if (frame != null) { |
416 | FrameBodyTALB body = (FrameBodyTALB) frame.getBody(); |
417 | text = body.getText(); |
418 | } |
419 | return text.trim(); |
420 | } |
421 | |
422 | public String getYearReleased() { |
423 | String text = ""; |
424 | AbstractID3v2Frame frame = getFrame("TYER"); |
425 | if (frame != null) { |
426 | FrameBodyTYER body = (FrameBodyTYER) frame.getBody(); |
427 | text = body.getText(); |
428 | } |
429 | return text.trim(); |
430 | } |
431 | |
432 | public String getSongComment() { |
433 | String text = ""; |
434 | AbstractID3v2Frame frame = getFrame("COMM" + ((char) 0) + "eng" + ((char) 0) + ""); |
435 | if (frame != null) { |
436 | FrameBodyCOMM body = (FrameBodyCOMM) frame.getBody(); |
437 | text = body.getText(); |
438 | } |
439 | return text.trim(); |
440 | } |
441 | |
442 | public String getSongGenre() { |
443 | String text = ""; |
444 | AbstractID3v2Frame frame = getFrame("TCON"); |
445 | if (frame != null) { |
446 | FrameBodyTCON body = (FrameBodyTCON) frame.getBody(); |
447 | text = body.getText(); |
448 | } |
449 | return text.trim(); |
450 | } |
451 | |
452 | public String getTrackNumberOnAlbum() { |
453 | String text = ""; |
454 | AbstractID3v2Frame frame = getFrame("TRCK"); |
455 | if (frame != null) { |
456 | FrameBodyTRCK body = (FrameBodyTRCK) frame.getBody(); |
457 | text = body.getText(); |
458 | } |
459 | return text.trim(); |
460 | } |
461 | |
462 | public String getSongLyric() { |
463 | String text = ""; |
464 | AbstractID3v2Frame frame = getFrame("SYLT"); |
465 | if (frame != null) { |
466 | FrameBodySYLT body = (FrameBodySYLT) frame.getBody(); |
467 | text = body.getLyric(); |
468 | } |
469 | if (text == "") { |
470 | frame = getFrame("USLT" + ((char) 0) + "eng" + ((char) 0) + ""); |
471 | if (frame != null) { |
472 | FrameBodyUSLT body = (FrameBodyUSLT) frame.getBody(); |
473 | text = body.getLyric(); |
474 | } |
475 | } |
476 | return text.trim(); |
477 | } |
478 | |
479 | public String getAuthorComposer() { |
480 | String text = ""; |
481 | AbstractID3v2Frame frame = getFrame("TCOM"); |
482 | if (frame != null) { |
483 | FrameBodyTCOM body = (FrameBodyTCOM) frame.getBody(); |
484 | text = body.getText(); |
485 | } |
486 | return text.trim(); |
487 | } |
488 | |
489 | public void setSongTitle(String songTitle) { |
490 | AbstractID3v2Frame field = getFrame("TIT2"); |
491 | if (field == null) { |
492 | field = new ID3v2_3Frame(new FrameBodyTIT2((byte) 0, songTitle.trim())); |
493 | setFrame(field); |
494 | } else { |
495 | ((FrameBodyTIT2) field.getBody()).setText(songTitle.trim()); |
496 | } |
497 | } |
498 | |
499 | public void setLeadArtist(String leadArtist) { |
500 | AbstractID3v2Frame field = getFrame("TPE1"); |
501 | if (field == null) { |
502 | field = new ID3v2_3Frame(new FrameBodyTPE1((byte) 0, leadArtist.trim())); |
503 | setFrame(field); |
504 | } else { |
505 | ((FrameBodyTPE1) field.getBody()).setText(leadArtist.trim()); |
506 | } |
507 | } |
508 | |
509 | public void setAlbumTitle(String albumTitle) { |
510 | AbstractID3v2Frame field = getFrame("TALB"); |
511 | if (field == null) { |
512 | field = new ID3v2_3Frame(new FrameBodyTALB((byte) 0, albumTitle.trim())); |
513 | setFrame(field); |
514 | } else { |
515 | ((FrameBodyTALB) field.getBody()).setText(albumTitle.trim()); |
516 | } |
517 | } |
518 | |
519 | public void setYearReleased(String yearReleased) { |
520 | AbstractID3v2Frame field = getFrame("TYER"); |
521 | if (field == null) { |
522 | field = new ID3v2_3Frame(new FrameBodyTYER((byte) 0, yearReleased.trim())); |
523 | setFrame(field); |
524 | } else { |
525 | ((FrameBodyTYER) field.getBody()).setText(yearReleased.trim()); |
526 | } |
527 | } |
528 | |
529 | public void setSongComment(String songComment) { |
530 | AbstractID3v2Frame field = getFrame("COMM"); |
531 | if (field == null) { |
532 | field = new ID3v2_3Frame(new FrameBodyCOMM((byte) 0, "ENG", "", songComment.trim())); |
533 | setFrame(field); |
534 | } else { |
535 | ((FrameBodyCOMM) field.getBody()).setText(songComment.trim()); |
536 | } |
537 | } |
538 | |
539 | public void setSongGenre(String songGenre) { |
540 | AbstractID3v2Frame field = getFrame("TCON"); |
541 | if (field == null) { |
542 | field = new ID3v2_3Frame(new FrameBodyTCON((byte) 0, songGenre.trim())); |
543 | setFrame(field); |
544 | } else { |
545 | ((FrameBodyTCON) field.getBody()).setText(songGenre.trim()); |
546 | } |
547 | } |
548 | |
549 | public void setTrackNumberOnAlbum(String trackNumberOnAlbum) { |
550 | AbstractID3v2Frame field = getFrame("TRCK"); |
551 | if (field == null) { |
552 | field = new ID3v2_3Frame(new FrameBodyTRCK((byte) 0, trackNumberOnAlbum.trim())); |
553 | setFrame(field); |
554 | } else { |
555 | ((FrameBodyTRCK) field.getBody()).setText(trackNumberOnAlbum.trim()); |
556 | } |
557 | } |
558 | |
559 | public void setSongLyric(String songLyrics) { |
560 | AbstractID3v2Frame field = getFrame("SYLT"); |
561 | if (field == null) { |
562 | field = new ID3v2_3Frame(new FrameBodyUSLT((byte) 0, "ENG", "", songLyrics.trim())); |
563 | setFrame(field); |
564 | } else { |
565 | ((FrameBodyUSLT) field.getBody()).setLyric(songLyrics.trim()); |
566 | } |
567 | } |
568 | |
569 | public void setAuthorComposer(String authorComposer) { |
570 | AbstractID3v2Frame field = getFrame("TCOM"); |
571 | if (field == null) { |
572 | field = new ID3v2_3Frame(new FrameBodyTCOM((byte) 0, authorComposer.trim())); |
573 | setFrame(field); |
574 | } else { |
575 | ((FrameBodyTCOM) field.getBody()).setText(authorComposer.trim()); |
576 | } |
577 | } |
578 | } |