| 1 | package org.farng.mp3.id3; |
| 2 | |
| 3 | import org.farng.mp3.InvalidTagException; |
| 4 | import org.farng.mp3.TagUtility; |
| 5 | |
| 6 | import java.io.IOException; |
| 7 | import java.io.RandomAccessFile; |
| 8 | |
| 9 | /** |
| 10 | * <p class=t> The headers of the frames are similar in their construction. They consist of one three character |
| 11 | * identifier (capital A-Z and 0-9) and one three byte size field, making a total of six bytes. The header is excluded |
| 12 | * from the size. Identifiers beginning with "X", "Y" and "Z" are for experimental use and free for everyone to use. |
| 13 | * Have in mind that someone else might have used the same identifier as you. All other identifiers are either used or |
| 14 | * reserved for future use. <i>This gives us 46656 combinations of frame identifiers. </i></p> |
| 15 | * <p/> |
| 16 | * <p class=t> The three character frame identifier is followed by a three byte size descriptor, making a total header |
| 17 | * size of six bytes in every frame. The size is calculated as framesize excluding frame identifier and size descriptor |
| 18 | * (frame size - 6). </p> |
| 19 | * <p/> |
| 20 | * <p class=t><i> The decision to have a 6 byte frame header was taken in an attempt to balance big frames against |
| 21 | * little overhead. One might think that it's stupid to optimize away a few bytes when the entire MP3-file is soo huge. |
| 22 | * On the other hand I thought it was really cool that most ID3v1 tags, when converted to ID3v2 was smaller than before. |
| 23 | * Size does matter. </i></p> |
| 24 | * <p/> |
| 25 | * <p class=t> There is no fixed order of the frames' appearance in the tag, although it is desired that the frames are |
| 26 | * arranged in order of significance concerning the recognition of the file. <i>The reason for this is to make it faster |
| 27 | * to search for a specific file by scanning the ID3v2 tags; an intelligent parser wouldn't have to keep reading the |
| 28 | * entire tag after having found that the file isn't the one being looked for.</i> An example of such order: <a |
| 29 | * href="#ufi">UFI</a>, <a href="#mci">MCI</a>, <a href="#tt2">TT2</a> ...<br> </p> |
| 30 | * <p/> |
| 31 | * <p class=t> A tag must contain at least one frame. A frame must be at least 1 byte big, excluding the 6-byte header. |
| 32 | * </p> |
| 33 | * <p/> |
| 34 | * <p class=t> If nothing else is said, a string is represented as <a href="#iso8859">ISO-8859-1</a> characters in the |
| 35 | * range $20 - $FF. All <a href="#unicode">unicode</a> strings use 16-bit unicode 2.0 (ISO/IEC 10646-1:1993, UCS-2). All |
| 36 | * numeric strings are always encoded as <a href="#iso8859">ISO-8859-1</a>. Terminated strings are terminated with $00 |
| 37 | * if encoded with <a href="#iso8859">ISO-8859-1</a> and $00 00 if encoded as unicode. If nothing else is said, newline |
| 38 | * characters are forbidden. In <a href="#iso8859">ISO-8859-1</a>, a new line is represented, when allowed, with $0A |
| 39 | * only. Frames that allow different types of text encoding have a text encoding description byte directly after the |
| 40 | * frame size. If <a href="#iso8859">ISO-8859-1</a> is used this byte should be $00, if <a href="#unicode">unicode</a> |
| 41 | * is used it should be $01. </p> |
| 42 | * <p/> |
| 43 | * <p class=t> The three byte language field is used to describe the language of the frame's content, according to <a |
| 44 | * href="#iso639-2">ISO-639-2</a>.<br> <i>ISO-639-1 is not used since its supported languages are just a subset of those |
| 45 | * in ISO-639-2.</i> </p> |
| 46 | * <p/> |
| 47 | * <p class=t> All <a href="#url">URL</a>s may be relative, e.g. "picture.png", "../doc.txt". </p> |
| 48 | * <p/> |
| 49 | * <p class=t> If a frame is longer than it should be, e.g. having more fields than specified in this document, that |
| 50 | * indicates that additions to the frame have been made in a later version of the ID3 standard. This is reflected by the |
| 51 | * revision number in the header of the tag.<br> <i>This allows us to fix our mistakes as well as introducing new |
| 52 | * features in the already existing frames. </i></p> |
| 53 | * |
| 54 | * @author Eric Farng |
| 55 | * @version $Revision: 1.6 $ |
| 56 | */ |
| 57 | public class ID3v2_2Frame extends AbstractID3v2Frame { |
| 58 | |
| 59 | /** |
| 60 | * Creates a new ID3v2_2Frame object. |
| 61 | */ |
| 62 | public ID3v2_2Frame() { |
| 63 | // base empty constructor |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Creates a new ID3v2_2Frame object. |
| 68 | */ |
| 69 | public ID3v2_2Frame(final AbstractID3v2FrameBody body) { |
| 70 | super(body); |
| 71 | } |
| 72 | |
| 73 | /** |
| 74 | * Creates a new ID3v2_2Frame object. |
| 75 | */ |
| 76 | public ID3v2_2Frame(final ID3v2_2Frame frame) { |
| 77 | super(frame); |
| 78 | } |
| 79 | |
| 80 | /** |
| 81 | * Creates a new ID3v2_3Frame object. |
| 82 | */ |
| 83 | public ID3v2_2Frame(final AbstractID3v2Frame frame) { |
| 84 | if (frame.getBody() == null) { |
| 85 | // do nothing |
| 86 | } else if (TagUtility.isID3v2_2FrameIdentifier(frame.getIdentifier())) { |
| 87 | this.setBody((AbstractID3v2FrameBody) TagUtility.copyObject(frame.getBody())); |
| 88 | // } else if (TagUtility.isID3v2_3FrameIdentifier(frame.getIdentifier())) { |
| 89 | // // @todo correctly convert tags |
| 90 | // this.setBody((AbstractID3v2FrameBody) TagUtility.copyObject(frame.getBody())); |
| 91 | // } else if (TagUtility.isID3v2_4FrameIdentifier(frame.getIdentifier())) { |
| 92 | // // @todo correctly convert tags |
| 93 | // this.setBody((AbstractID3v2FrameBody) TagUtility.copyObject(frame.getBody())); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | /** |
| 98 | * Creates a new ID3v2_2Frame object. |
| 99 | */ |
| 100 | public ID3v2_2Frame(final RandomAccessFile file) throws IOException, InvalidTagException { |
| 101 | this.read(file); |
| 102 | } |
| 103 | |
| 104 | public int getSize() { |
| 105 | return this.getBody().getSize() + 3 + 3; |
| 106 | } |
| 107 | |
| 108 | public void read(final RandomAccessFile file) throws IOException, InvalidTagException { |
| 109 | final byte[] buffer = new byte[3]; |
| 110 | |
| 111 | // lets scan for a non-zero byte; |
| 112 | long filePointer; |
| 113 | byte b; |
| 114 | do { |
| 115 | filePointer = file.getFilePointer(); |
| 116 | b = file.readByte(); |
| 117 | org.farng.mp3.id3.AbstractID3v2.incrementPaddingCounter(); |
| 118 | } while (b == 0); |
| 119 | file.seek(filePointer); |
| 120 | org.farng.mp3.id3.AbstractID3v2.decrementPaddingCounter(); |
| 121 | |
| 122 | // read the 3 chracter identifier |
| 123 | file.read(buffer, 0, 3); |
| 124 | final String identifier = new String(buffer, 0, 3); |
| 125 | |
| 126 | // is this a valid identifier? |
| 127 | if (isValidID3v2FrameIdentifier(identifier) == false) { |
| 128 | file.seek(file.getFilePointer() - 2); |
| 129 | throw new InvalidTagException(identifier + " is not a valid ID3v2.20 frame"); |
| 130 | } |
| 131 | this.setBody(readBody(identifier, file)); |
| 132 | } |
| 133 | |
| 134 | public void write(final RandomAccessFile file) throws IOException { |
| 135 | final byte[] buffer = new byte[4]; |
| 136 | final String str = TagUtility.truncate(getIdentifier(), 3); |
| 137 | for (int i = 0; i < str.length(); i++) { |
| 138 | buffer[i] = (byte) str.charAt(i); |
| 139 | } |
| 140 | file.write(buffer, 0, str.length()); |
| 141 | this.getBody().write(file); |
| 142 | } |
| 143 | } |