| 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 | } |