| 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 expandable. </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 section 5. </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's format and content, |
| 26 | * and 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 to files encoded with <a href="#mpeg">MPEG-2 |
| 32 | * layer I, MPEG-2 layer II, MPEG-2 layer III</a> and MPEG-2.5, but may work with other types of encoded audio. </p> |
| 33 | * <p/> |
| 34 | * <p class=t> The bitorder in ID3v2 is most significant bit first (MSB). The byteorder in multibyte numbers is most |
| 35 | * significant byte first (e.g. $12345678 would be encoded $12 34 56 78). </p> |
| 36 | * <p/> |
| 37 | * <p class=t> It is permitted to include padding after all the final frame (at the end of the ID3 tag), making the size |
| 38 | * of all the frames together smaller than the size given in the head of the tag. A possible purpose of this padding is |
| 39 | * to allow for adding a few additional frames or enlarge existing frames within the tag without having to rewrite the |
| 40 | * entire file. The value of the padding bytes must be $00.<br> </p> |
| 41 | * <p/> |
| 42 | * <p class=t> <i>Padding is good as it increases the write speed when there is already a tag present in a file. If the |
| 43 | * new tag is one byte longer than the previous tag, than the extra byte can be taken from the padding, instead of |
| 44 | * having to shift the entire file one byte. Padding is of course bad in that it increases the size of the file, but if |
| 45 | * the amount of padding is wisely chosen (with clustersize in mind), the impact on filesystems will be virtually none. |
| 46 | * As the contents is $00, it is also easy for modems and other transmission devices/protocols to compress the padding. |
| 47 | * Having a $00 filled padding also increases the ability to recover erroneous tags.</i> </p> <p class=t> The ID3v2 tag |
| 48 | * header, which should be the first information in the file, is 10 bytes as follows: </p> |
| 49 | * <p/> |
| 50 | * <p><center> <table border=0> <tr><td nowrap>ID3/file identifier</td><td rowspan=3> </td><td |
| 51 | * width="100%">"ID3"</td></tr> <tr><td>ID3 version</td><td>$02 00</td></tr> <tr><td>ID3 |
| 52 | * flags</td><td>%xx000000</td></tr> <tr><td>ID3 size</td><td>4 * </td><td>%0xxxxxxx</td></tr> </table> </center> |
| 53 | * <p/> |
| 54 | * <p class=t> The first three bytes of the tag are always "ID3" to indicate that this is an ID3 tag, directly followed |
| 55 | * by the two version bytes. The first byte of ID3 version is it's major version, while the second byte is its revision |
| 56 | * number. All revisions are backwards compatible while major versions are not. If software with ID3v2 and below support |
| 57 | * should encounter version three or higher it should simply ignore the whole tag. Version and revision will never be |
| 58 | * $FF. </p> |
| 59 | * <p/> |
| 60 | * <p class=t><i> In the first draft of ID3v2 the identifier was "TAG", just as in ID3v1. It was later changed to "MP3" |
| 61 | * as I thought of the ID3v2 as the fileheader MP3 had always been missing. When it became appearant than ID3v2 was |
| 62 | * going towards a general purpose audio header the identifier was changed to "ID3". </i></p> |
| 63 | * <p/> |
| 64 | * <p class=t> The first bit (bit 7) in the 'ID3 flags' is indicating whether or not <a |
| 65 | * href="#sec5">unsynchronisation</a> is used; a set bit indicates usage. </p> |
| 66 | * <p/> |
| 67 | * <p class=t> The second bit (bit 6) is indicating whether or not compression is used; a set bit indicates usage. Since |
| 68 | * no compression scheme has been decided yet, the ID3 decoder (for now) should just ignore the entire tag if the |
| 69 | * compression bit is set. </p> |
| 70 | * <p/> |
| 71 | * <p class=t><i> Currently, zlib compression is being considered for the compression, in an effort to stay out of the |
| 72 | * all-too-common marsh of patent trouble. Have a look at the additions draft for the latest developments. </i></p> |
| 73 | * <p/> |
| 74 | * <p class=t> The ID3 tag size is encoded with four bytes where the first bit (bit 7) is set to zero in every byte, |
| 75 | * making a total of 28 bits. The zeroed bits are ignored, so a 257 bytes long tag is represented as $00 00 02 01. </p> |
| 76 | * <p/> |
| 77 | * <p class=t><i> We really gave it a second thought several times before we introduced these awkward size descriptions. |
| 78 | * The reason is that we thought it would be even worse to have a file header with no set size (as we wanted to |
| 79 | * unsynchronise the header if there were any false synchronisations in it). An easy way of calculating the tag size is |
| 80 | * A*2^21+B*2^14+C*2^7+D = A*2097152+B*16384+C*128+D, where A is the first byte, B the second, C the third and D the |
| 81 | * fourth byte. </i></p> |
| 82 | * <p/> |
| 83 | * <p class=t> The ID3 tag size is the size of the complete tag after unsychronisation, including padding, excluding the |
| 84 | * header (total tag size - 10). The reason to use 28 bits (representing up to 256MB) for size description is that we |
| 85 | * don't want to run out of space here. </p> |
| 86 | * <p/> |
| 87 | * <p class=t> An ID3v2 tag can be detected with the following pattern:<br> $49 44 33 yy yy xx |
| 88 | * zz zz zz zz <br> Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80. </p> |
| 89 | * |
| 90 | * @author Eric Farng |
| 91 | * @version $Revision: 1.5 $ |
| 92 | */ |
| 93 | public class ID3v2_2 extends AbstractID3v2 { |
| 94 | |
| 95 | protected boolean compression = false; |
| 96 | protected boolean unsynchronization = false; |
| 97 | |
| 98 | /** |
| 99 | * Creates a new ID3v2_2 object. |
| 100 | */ |
| 101 | public ID3v2_2() { |
| 102 | super(); |
| 103 | setMajorVersion((byte) 2); |
| 104 | setRevision((byte) 2); |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * Creates a new ID3v2_2 object. |
| 109 | */ |
| 110 | public ID3v2_2(final ID3v2_2 copyObject) { |
| 111 | super(copyObject); |
| 112 | this.compression = copyObject.compression; |
| 113 | this.unsynchronization = copyObject.unsynchronization; |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Creates a new ID3v2_2 object. |
| 118 | */ |
| 119 | public ID3v2_2(final AbstractMP3Tag mp3tag) { |
| 120 | if (mp3tag != null) { |
| 121 | final ID3v2_4 convertedTag; |
| 122 | if ((mp3tag instanceof ID3v2_3 == false) && (mp3tag instanceof ID3v2_2 == true)) { |
| 123 | throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument"); |
| 124 | } else if (mp3tag instanceof ID3v2_4) { |
| 125 | convertedTag = (ID3v2_4) mp3tag; |
| 126 | } else { |
| 127 | convertedTag = new ID3v2_4(mp3tag); |
| 128 | } |
| 129 | this.compression = convertedTag.compression; |
| 130 | this.unsynchronization = convertedTag.unsynchronization; |
| 131 | final AbstractID3v2 id3tag = convertedTag; |
| 132 | final Iterator iterator = id3tag.getFrameIterator(); |
| 133 | AbstractID3v2Frame frame; |
| 134 | ID3v2_2Frame newFrame; |
| 135 | while (iterator.hasNext()) { |
| 136 | frame = (AbstractID3v2Frame) iterator.next(); |
| 137 | newFrame = new ID3v2_2Frame(frame); |
| 138 | this.setFrame(newFrame); |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * Creates a new ID3v2_2 object. |
| 145 | */ |
| 146 | public ID3v2_2(final RandomAccessFile file) throws TagException, IOException { |
| 147 | this.read(file); |
| 148 | } |
| 149 | |
| 150 | public String getIdentifier() { |
| 151 | return "ID3v2_2.20"; |
| 152 | } |
| 153 | |
| 154 | public int getSize() { |
| 155 | int size = 3 + 2 + 1 + 4; |
| 156 | final Iterator iterator = getFrameIterator(); |
| 157 | ID3v2_2Frame frame; |
| 158 | while (iterator.hasNext()) { |
| 159 | frame = (ID3v2_2Frame) iterator.next(); |
| 160 | size += frame.getSize(); |
| 161 | } |
| 162 | return size; |
| 163 | } |
| 164 | |
| 165 | public void append(final AbstractMP3Tag tag) { |
| 166 | if (tag instanceof ID3v2_2) { |
| 167 | this.unsynchronization = ((ID3v2_2) tag).unsynchronization; |
| 168 | this.compression = ((ID3v2_2) tag).compression; |
| 169 | } |
| 170 | super.append(tag); |
| 171 | } |
| 172 | |
| 173 | public boolean equals(final Object obj) { |
| 174 | if ((obj instanceof ID3v2_2) == false) { |
| 175 | return false; |
| 176 | } |
| 177 | final ID3v2_2 id3v2_2 = (ID3v2_2) obj; |
| 178 | if (this.compression != id3v2_2.compression) { |
| 179 | return false; |
| 180 | } |
| 181 | if (this.unsynchronization != id3v2_2.unsynchronization) { |
| 182 | return false; |
| 183 | } |
| 184 | return super.equals(obj); |
| 185 | } |
| 186 | |
| 187 | public void overwrite(final AbstractMP3Tag tag) { |
| 188 | if (tag instanceof ID3v2_2) { |
| 189 | this.unsynchronization = ((ID3v2_2) tag).unsynchronization; |
| 190 | this.compression = ((ID3v2_2) tag).compression; |
| 191 | } |
| 192 | super.overwrite(tag); |
| 193 | } |
| 194 | |
| 195 | public void read(final RandomAccessFile file) throws TagException, IOException { |
| 196 | final int size; |
| 197 | ID3v2_2Frame next; |
| 198 | final byte[] buffer = new byte[4]; |
| 199 | if (seek(file) == false) { |
| 200 | throw new TagNotFoundException("ID3v2.20 tag not found"); |
| 201 | } |
| 202 | |
| 203 | // read the major and minor @version number & flags byte |
| 204 | file.read(buffer, 0, 3); |
| 205 | if ((buffer[0] != 2) || (buffer[1] != 0)) { |
| 206 | throw new TagNotFoundException(getIdentifier() + " tag not found"); |
| 207 | } |
| 208 | setMajorVersion(buffer[0]); |
| 209 | setRevision(buffer[1]); |
| 210 | this.unsynchronization = (buffer[2] & TagConstant.MASK_V22_UNSYNCHRONIZATION) != 0; |
| 211 | this.compression = (buffer[2] & TagConstant.MASK_V22_COMPRESSION) != 0; |
| 212 | |
| 213 | // read the size |
| 214 | file.read(buffer, 0, 4); |
| 215 | size = byteArrayToSize(buffer); |
| 216 | this.clearFrameMap(); |
| 217 | final long filePointer = file.getFilePointer(); |
| 218 | |
| 219 | // read all frames |
| 220 | this.setFileReadBytes(size); |
| 221 | resetPaddingCounter(); |
| 222 | while ((file.getFilePointer() - filePointer) <= size) { |
| 223 | try { |
| 224 | next = new ID3v2_2Frame(file); |
| 225 | final String id = next.getIdentifier(); |
| 226 | if (this.hasFrame(id)) { |
| 227 | this.appendDuplicateFrameId(id + "; "); |
| 228 | this.incrementDuplicateBytes(getFrame(id).getSize()); |
| 229 | } |
| 230 | this.setFrame(next); |
| 231 | } catch (InvalidTagException ex) { |
| 232 | if (ex.getMessage().equals("Found empty frame")) { |
| 233 | this.incrementEmptyFrameBytes(10); |
| 234 | } else { |
| 235 | this.incrementInvalidFrameBytes(); |
| 236 | } |
| 237 | } |
| 238 | } |
| 239 | this.setPaddingSize(getPaddingCounter()); |
| 240 | |
| 241 | /** |
| 242 | * int newSize = this.getSize(); if ((this.padding + newSize - 10) != |
| 243 | * size) { System.out.println("WARNING: Tag sizes don't add up"); |
| 244 | * System.out.println("ID3v2.20 tag size : " + newSize); |
| 245 | * System.out.println("ID3v2.20 padding : " + this.padding); |
| 246 | * System.out.println("ID3v2.20 total : " + (this.padding + newSize)); |
| 247 | * System.out.println("ID3v2.20 file size: " + size); } |
| 248 | */ |
| 249 | } |
| 250 | |
| 251 | public boolean seek(final RandomAccessFile file) throws IOException { |
| 252 | final byte[] buffer = new byte[3]; |
| 253 | file.seek(0); |
| 254 | |
| 255 | // read the tag if it exists |
| 256 | file.read(buffer, 0, 3); |
| 257 | final String tag = new String(buffer, 0, 3); |
| 258 | if (tag.equals("ID3") == false) { |
| 259 | return false; |
| 260 | } |
| 261 | |
| 262 | // read the major and minor @version number |
| 263 | file.read(buffer, 0, 2); |
| 264 | |
| 265 | // read back the @version bytes so we can read and save them later |
| 266 | file.seek(file.getFilePointer() - 2); |
| 267 | return ((buffer[0] == 2) && (buffer[1] == 0)); |
| 268 | } |
| 269 | |
| 270 | public String toString() { |
| 271 | final Iterator iterator = this.getFrameIterator(); |
| 272 | ID3v2_2Frame frame; |
| 273 | String str = getIdentifier() + " - " + this.getSize() + " bytes\n"; |
| 274 | str += ("compression = " + this.compression + "\n"); |
| 275 | str += ("unsynchronization = " + this.unsynchronization + "\n"); |
| 276 | while (iterator.hasNext()) { |
| 277 | frame = (ID3v2_2Frame) iterator.next(); |
| 278 | str += (frame.toString() + "\n"); |
| 279 | } |
| 280 | return str + "\n"; |
| 281 | } |
| 282 | |
| 283 | public void write(final AbstractMP3Tag tag) { |
| 284 | if (tag instanceof ID3v2_2) { |
| 285 | this.unsynchronization = ((ID3v2_2) tag).unsynchronization; |
| 286 | this.compression = ((ID3v2_2) tag).compression; |
| 287 | } |
| 288 | super.write(tag); |
| 289 | } |
| 290 | |
| 291 | public void write(final RandomAccessFile file) throws IOException { |
| 292 | final String str; |
| 293 | ID3v2_2Frame frame; |
| 294 | final Iterator iterator; |
| 295 | final byte[] buffer = new byte[6]; |
| 296 | final MP3File mp3 = new MP3File(); |
| 297 | mp3.seekMP3Frame(file); |
| 298 | final long mp3start = file.getFilePointer(); |
| 299 | file.seek(0); |
| 300 | |
| 301 | // write the first 10 tag bytes |
| 302 | str = "ID3"; |
| 303 | for (int i = 0; i < str.length(); i++) { |
| 304 | buffer[i] = (byte) str.charAt(i); |
| 305 | } |
| 306 | buffer[3] = 2; |
| 307 | buffer[4] = 0; |
| 308 | if (this.unsynchronization) { |
| 309 | buffer[5] |= TagConstant.MASK_V22_UNSYNCHRONIZATION; |
| 310 | } |
| 311 | if (this.compression) { |
| 312 | buffer[5] |= TagConstant.MASK_V22_COMPRESSION; |
| 313 | } |
| 314 | file.write(buffer); |
| 315 | |
| 316 | //write size; |
| 317 | file.write(sizeToByteArray((int) mp3start - 10)); |
| 318 | |
| 319 | // write all frames |
| 320 | iterator = this.getFrameIterator(); |
| 321 | while (iterator.hasNext()) { |
| 322 | frame = (ID3v2_2Frame) iterator.next(); |
| 323 | frame.write(file); |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | public String getSongTitle() { |
| 328 | String text = ""; |
| 329 | AbstractID3v2Frame frame = getFrame("TIT2"); |
| 330 | if (frame != null) { |
| 331 | FrameBodyTIT2 body = (FrameBodyTIT2) frame.getBody(); |
| 332 | text = body.getText(); |
| 333 | } |
| 334 | return text.trim(); |
| 335 | } |
| 336 | |
| 337 | public String getLeadArtist() { |
| 338 | String text = ""; |
| 339 | AbstractID3v2Frame frame = getFrame("TPE1"); |
| 340 | if (frame != null) { |
| 341 | FrameBodyTPE1 body = (FrameBodyTPE1) frame.getBody(); |
| 342 | text = body.getText(); |
| 343 | } |
| 344 | return text.trim(); |
| 345 | } |
| 346 | |
| 347 | public String getAlbumTitle() { |
| 348 | String text = ""; |
| 349 | AbstractID3v2Frame frame = getFrame("TALB"); |
| 350 | if (frame != null) { |
| 351 | FrameBodyTALB body = (FrameBodyTALB) frame.getBody(); |
| 352 | text = body.getText(); |
| 353 | } |
| 354 | return text.trim(); |
| 355 | } |
| 356 | |
| 357 | public String getYearReleased() { |
| 358 | String text = ""; |
| 359 | AbstractID3v2Frame frame = getFrame("TYER"); |
| 360 | if (frame != null) { |
| 361 | FrameBodyTYER body = (FrameBodyTYER) frame.getBody(); |
| 362 | text = body.getText(); |
| 363 | } |
| 364 | return text.trim(); |
| 365 | } |
| 366 | |
| 367 | public String getSongComment() { |
| 368 | String text = ""; |
| 369 | AbstractID3v2Frame frame = getFrame("COMM" + ((char) 0) + "eng" + ((char) 0) + ""); |
| 370 | if (frame != null) { |
| 371 | FrameBodyCOMM body = (FrameBodyCOMM) frame.getBody(); |
| 372 | text = body.getText(); |
| 373 | } |
| 374 | return text.trim(); |
| 375 | } |
| 376 | |
| 377 | public String getSongGenre() { |
| 378 | String text = ""; |
| 379 | AbstractID3v2Frame frame = getFrame("TCON"); |
| 380 | if (frame != null) { |
| 381 | FrameBodyTCON body = (FrameBodyTCON) frame.getBody(); |
| 382 | text = body.getText(); |
| 383 | } |
| 384 | return text.trim(); |
| 385 | } |
| 386 | |
| 387 | public String getTrackNumberOnAlbum() { |
| 388 | String text = ""; |
| 389 | AbstractID3v2Frame frame = getFrame("TRCK"); |
| 390 | if (frame != null) { |
| 391 | FrameBodyTRCK body = (FrameBodyTRCK) frame.getBody(); |
| 392 | text = body.getText(); |
| 393 | } |
| 394 | return text.trim(); |
| 395 | } |
| 396 | |
| 397 | public String getSongLyric() { |
| 398 | String text = ""; |
| 399 | AbstractID3v2Frame frame = getFrame("SYLT"); |
| 400 | if (frame != null) { |
| 401 | FrameBodySYLT body = (FrameBodySYLT) frame.getBody(); |
| 402 | text = body.getLyric(); |
| 403 | } |
| 404 | if (text == "") { |
| 405 | frame = getFrame("USLT" + ((char) 0) + "eng" + ((char) 0) + ""); |
| 406 | if (frame != null) { |
| 407 | FrameBodyUSLT body = (FrameBodyUSLT) frame.getBody(); |
| 408 | text = body.getLyric(); |
| 409 | } |
| 410 | } |
| 411 | return text.trim(); |
| 412 | } |
| 413 | |
| 414 | public String getAuthorComposer() { |
| 415 | String text = ""; |
| 416 | AbstractID3v2Frame frame = getFrame("TCOM"); |
| 417 | if (frame != null) { |
| 418 | FrameBodyTCOM body = (FrameBodyTCOM) frame.getBody(); |
| 419 | text = body.getText(); |
| 420 | } |
| 421 | return text.trim(); |
| 422 | } |
| 423 | |
| 424 | public void setSongTitle(String songTitle) { |
| 425 | AbstractID3v2Frame field = getFrame("TIT2"); |
| 426 | if (field == null) { |
| 427 | field = new ID3v2_2Frame(new FrameBodyTIT2((byte) 0, songTitle.trim())); |
| 428 | setFrame(field); |
| 429 | } else { |
| 430 | ((FrameBodyTIT2) field.getBody()).setText(songTitle.trim()); |
| 431 | } |
| 432 | } |
| 433 | |
| 434 | public void setLeadArtist(String leadArtist) { |
| 435 | AbstractID3v2Frame field = getFrame("TPE1"); |
| 436 | if (field == null) { |
| 437 | field = new ID3v2_2Frame(new FrameBodyTPE1((byte) 0, leadArtist.trim())); |
| 438 | setFrame(field); |
| 439 | } else { |
| 440 | ((FrameBodyTPE1) field.getBody()).setText(leadArtist.trim()); |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | public void setAlbumTitle(String albumTitle) { |
| 445 | AbstractID3v2Frame field = getFrame("TALB"); |
| 446 | if (field == null) { |
| 447 | field = new ID3v2_2Frame(new FrameBodyTALB((byte) 0, albumTitle.trim())); |
| 448 | setFrame(field); |
| 449 | } else { |
| 450 | ((FrameBodyTALB) field.getBody()).setText(albumTitle.trim()); |
| 451 | } |
| 452 | } |
| 453 | |
| 454 | public void setYearReleased(String yearReleased) { |
| 455 | AbstractID3v2Frame field = getFrame("TYER"); |
| 456 | if (field == null) { |
| 457 | field = new ID3v2_2Frame(new FrameBodyTYER((byte) 0, yearReleased.trim())); |
| 458 | setFrame(field); |
| 459 | } else { |
| 460 | ((FrameBodyTYER) field.getBody()).setText(yearReleased.trim()); |
| 461 | } |
| 462 | } |
| 463 | |
| 464 | public void setSongComment(String songComment) { |
| 465 | AbstractID3v2Frame field = getFrame("COMM"); |
| 466 | if (field == null) { |
| 467 | field = new ID3v2_2Frame(new FrameBodyCOMM((byte) 0, "eng", "", songComment.trim())); |
| 468 | setFrame(field); |
| 469 | } else { |
| 470 | ((FrameBodyCOMM) field.getBody()).setText(songComment.trim()); |
| 471 | } |
| 472 | } |
| 473 | |
| 474 | public void setSongGenre(String songGenre) { |
| 475 | AbstractID3v2Frame field = getFrame("TCON"); |
| 476 | if (field == null) { |
| 477 | field = new ID3v2_2Frame(new FrameBodyTCON((byte) 0, songGenre.trim())); |
| 478 | setFrame(field); |
| 479 | } else { |
| 480 | ((FrameBodyTCON) field.getBody()).setText(songGenre.trim()); |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | public void setTrackNumberOnAlbum(String trackNumberOnAlbum) { |
| 485 | AbstractID3v2Frame field = getFrame("TRCK"); |
| 486 | if (field == null) { |
| 487 | field = new ID3v2_2Frame(new FrameBodyTRCK((byte) 0, trackNumberOnAlbum.trim())); |
| 488 | setFrame(field); |
| 489 | } else { |
| 490 | ((FrameBodyTRCK) field.getBody()).setText(trackNumberOnAlbum.trim()); |
| 491 | } |
| 492 | } |
| 493 | |
| 494 | public void setSongLyric(String songLyrics) { |
| 495 | AbstractID3v2Frame field = getFrame("USLT"); |
| 496 | if (field == null) { |
| 497 | field = new ID3v2_2Frame(new FrameBodyUSLT((byte) 0, "ENG", "", songLyrics.trim())); |
| 498 | setFrame(field); |
| 499 | } else { |
| 500 | ((FrameBodyUSLT) field.getBody()).setLyric(songLyrics.trim()); |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | public void setAuthorComposer(String authorComposer) { |
| 505 | AbstractID3v2Frame field = getFrame("TCOM"); |
| 506 | if (field == null) { |
| 507 | field = new ID3v2_2Frame(new FrameBodyTCOM((byte) 0, authorComposer.trim())); |
| 508 | setFrame(field); |
| 509 | } else { |
| 510 | ((FrameBodyTCOM) field.getBody()).setText(authorComposer.trim()); |
| 511 | } |
| 512 | } |
| 513 | } |