| 1 | package org.farng.mp3.lyrics3; |
| 2 | |
| 3 | import org.farng.mp3.AbstractMP3Fragment; |
| 4 | import org.farng.mp3.InvalidTagException; |
| 5 | import org.farng.mp3.TagException; |
| 6 | import org.farng.mp3.TagOptionSingleton; |
| 7 | import org.farng.mp3.TagUtility; |
| 8 | import org.farng.mp3.id3.AbstractFrameBodyTextInformation; |
| 9 | import org.farng.mp3.id3.AbstractID3v2Frame; |
| 10 | import org.farng.mp3.id3.FrameBodyCOMM; |
| 11 | import org.farng.mp3.id3.FrameBodySYLT; |
| 12 | import org.farng.mp3.id3.FrameBodyUSLT; |
| 13 | |
| 14 | import java.io.IOException; |
| 15 | import java.io.RandomAccessFile; |
| 16 | |
| 17 | /** |
| 18 | * <TABLE border=0> <TBODY> <TR> |
| 19 | * <p/> |
| 20 | * <TD class=h2>Defined fields</TD></TR></TBODY></TABLE> <TABLE border=0> <TBODY> <TR vAlign=top> <TD> <P>The following |
| 21 | * list is a list of currently defined field IDs. More fields might be added if needed on newer versions of the Lyrics3 |
| 22 | * v2.00 specifications. Unknown fields should be ignored.</P> <TABLE> <TBODY> |
| 23 | * <p/> |
| 24 | * <TR> <TD><U>ID</U></TD> <TD><U>Max size</U></TD> <TD><U>Description</U></TD></TR> <TR vAlign=top> <TD><B>IND</B></TD> |
| 25 | * <TD>00002</TD> |
| 26 | * <p/> |
| 27 | * <TD>Indications field. This is always two characters big in v2.00, but might be bigger in a future standard. The |
| 28 | * first byte indicates wether or not a lyrics field is present. "1" for present and "0" for otherwise. The second |
| 29 | * character indicates if there is a timestamp in the lyrics. Again "1" for yes and "0" for no.</TD></TR> <TR |
| 30 | * vAlign=top> <TD><B>LYR</B></TD> <TD>99999</TD> <TD>Lyrics multi line text. Timestamps can be used anywhere in the |
| 31 | * text in any order. Timestamp format is [mm:ss] (no spaces allowed in the timestamps).</TD></TR> <TR vAlign=top> |
| 32 | * <TD><B>INF</B></TD> |
| 33 | * <p/> |
| 34 | * <TD>99999</TD> <TD>Additional information multi line text.</TD></TR> <TR vAlign=top> <TD><B>AUT</B></TD> |
| 35 | * <TD>00250</TD> <TD>Lyrics/Music Author name.</TD></TR> |
| 36 | * <p/> |
| 37 | * <TR vAlign=top> <TD><B>EAL</B></TD> <TD>00250</TD> <TD>Extended Album name.</TD></TR> <TR vAlign=top> |
| 38 | * <TD><B>EAR</B></TD> <TD>00250</TD> |
| 39 | * <p/> |
| 40 | * <TD>Extended Artist name.</TD></TR> <TR vAlign=top> <TD><B>ETT</B></TD> <TD>00250</TD> <TD>Extended Track |
| 41 | * Title.</TD></TR> <TR vAlign=top> <TD><B>IMG</B></TD> |
| 42 | * <p/> |
| 43 | * <TD>99999</TD> <TD>Link to an image files (BMP or JPG format). Image lines include filename, description and |
| 44 | * timestamp separated by delimiter - two ASCII chars 124 ("||"). Description and timestamp are optional, but if |
| 45 | * timestamp is used, and there is no description, two delimiters ("||||") should be used between the filename and the |
| 46 | * timestamp. Multiple images are allowed by using a [CR][LF] delimiter between each image line. No [CR][LF] is needed |
| 47 | * after the last image line. Number of images is not limited (except by the field size).<BR><B>Filename</B> can be in |
| 48 | * one of these formats: <UL> <LI>Filename only - when the image is located in the same path as the MP3 file (preferred, |
| 49 | * since if you move the mp3 file this will still be correct) <LI>Relative Path + Filename - when the image is located |
| 50 | * in a subdirectory below the MP3 file (i.e. images\cover.jpg) <LI>Full path + Filename - when the image is located in |
| 51 | * a totally different path or drive. This will not work if the image is moved or drive letters has changed, and so |
| 52 | * should be avoided if possible (i.e. c:\images\artist.jpg)</LI></UL><B>Description</B> can be up to 250 chars |
| 53 | * long.<BR><B>Timestamp</B> must be formatted like the lyrics timestamp which is "[mm:ss]". If an image has a |
| 54 | * timestamp, then the visible image will automatically switch to that image on the timestamp play time, just the same |
| 55 | * as the selected lyrics line is switched based on timestamps.</TD></TR></TBODY></TABLE> |
| 56 | * <p/> |
| 57 | * </TD></TR></TBODY></TABLE> * |
| 58 | * |
| 59 | * @author Eric Farng |
| 60 | * @version $Revision: 1.5 $ |
| 61 | */ |
| 62 | public class Lyrics3v2Field extends AbstractMP3Fragment { |
| 63 | |
| 64 | /** |
| 65 | * Creates a new Lyrics3v2Field object. |
| 66 | */ |
| 67 | public Lyrics3v2Field() { |
| 68 | // base empty constructor |
| 69 | } |
| 70 | |
| 71 | /** |
| 72 | * Creates a new Lyrics3v2Field object. |
| 73 | */ |
| 74 | public Lyrics3v2Field(final Lyrics3v2Field copyObject) { |
| 75 | super(copyObject); |
| 76 | } |
| 77 | |
| 78 | /** |
| 79 | * Creates a new Lyrics3v2Field object. |
| 80 | */ |
| 81 | public Lyrics3v2Field(final AbstractLyrics3v2FieldBody body) { |
| 82 | super(body); |
| 83 | } |
| 84 | |
| 85 | /** |
| 86 | * Creates a new Lyrics3v2Field object. |
| 87 | */ |
| 88 | public Lyrics3v2Field(final AbstractID3v2Frame frame) throws TagException { |
| 89 | final AbstractFrameBodyTextInformation textFrame; |
| 90 | final String text; |
| 91 | final String frameIdentifier = frame.getIdentifier(); |
| 92 | if (frameIdentifier.startsWith("USLT")) { |
| 93 | this.setBody(new FieldBodyLYR("")); |
| 94 | ((FieldBodyLYR) this.getBody()).addLyric((FrameBodyUSLT) frame.getBody()); |
| 95 | } else if (frameIdentifier.startsWith("SYLT")) { |
| 96 | this.setBody(new FieldBodyLYR("")); |
| 97 | ((FieldBodyLYR) this.getBody()).addLyric((FrameBodySYLT) frame.getBody()); |
| 98 | } else if (frameIdentifier.startsWith("COMM")) { |
| 99 | text = new String(((FrameBodyCOMM) frame.getBody()).getText()); |
| 100 | this.setBody(new FieldBodyINF(text)); |
| 101 | } else if (frameIdentifier.equals("TCOM")) { |
| 102 | textFrame = (AbstractFrameBodyTextInformation) frame.getBody(); |
| 103 | this.setBody(new FieldBodyAUT("")); |
| 104 | if ((textFrame != null) && (((textFrame.getText())).length() > 0)) { |
| 105 | this.setBody(new FieldBodyAUT((textFrame.getText()))); |
| 106 | } |
| 107 | } else if (frameIdentifier.equals("TALB")) { |
| 108 | textFrame = (AbstractFrameBodyTextInformation) frame.getBody(); |
| 109 | if ((textFrame != null) && ((textFrame.getText()).length() > 0)) { |
| 110 | this.setBody(new FieldBodyEAL((textFrame.getText()))); |
| 111 | } |
| 112 | } else if (frameIdentifier.equals("TPE1")) { |
| 113 | textFrame = (AbstractFrameBodyTextInformation) frame.getBody(); |
| 114 | if ((textFrame != null) && ((textFrame.getText()).length() > 0)) { |
| 115 | this.setBody(new FieldBodyEAR((textFrame.getText()))); |
| 116 | } |
| 117 | } else if (frameIdentifier.equals("TIT2")) { |
| 118 | textFrame = (AbstractFrameBodyTextInformation) frame.getBody(); |
| 119 | if ((textFrame != null) && ((textFrame.getText()).length() > 0)) { |
| 120 | this.setBody(new FieldBodyETT((textFrame.getText()))); |
| 121 | } |
| 122 | } else { |
| 123 | throw new TagException("Cannot create Lyrics3v2 field from given ID3v2 frame"); |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Creates a new Lyrics3v2Field object. |
| 129 | */ |
| 130 | public Lyrics3v2Field(final RandomAccessFile file) throws InvalidTagException, IOException { |
| 131 | this.read(file); |
| 132 | } |
| 133 | |
| 134 | public String getIdentifier() { |
| 135 | if (this.getBody() == null) { |
| 136 | return ""; |
| 137 | } |
| 138 | return this.getBody().getIdentifier(); |
| 139 | } |
| 140 | |
| 141 | public int getSize() { |
| 142 | return this.getBody().getSize() + 5 + getIdentifier().length(); |
| 143 | } |
| 144 | |
| 145 | public void read(final RandomAccessFile file) throws InvalidTagException, IOException { |
| 146 | final byte[] buffer = new byte[6]; |
| 147 | |
| 148 | // lets scan for a non-zero byte; |
| 149 | long filePointer; |
| 150 | byte b; |
| 151 | do { |
| 152 | filePointer = file.getFilePointer(); |
| 153 | b = file.readByte(); |
| 154 | } while (b == 0); |
| 155 | file.seek(filePointer); |
| 156 | |
| 157 | // read the 3 character ID |
| 158 | file.read(buffer, 0, 3); |
| 159 | final String identifier = new String(buffer, 0, 3); |
| 160 | |
| 161 | // is this a valid identifier? |
| 162 | if (TagUtility.isLyrics3v2FieldIdentifier(identifier) == false) { |
| 163 | throw new InvalidTagException(identifier + " is not a valid ID3v2.4 frame"); |
| 164 | } |
| 165 | this.setBody(readBody(identifier, file)); |
| 166 | } |
| 167 | |
| 168 | public String toString() { |
| 169 | if (this.getBody() == null) { |
| 170 | return ""; |
| 171 | } |
| 172 | return this.getBody().toString(); |
| 173 | } |
| 174 | |
| 175 | public void write(final RandomAccessFile file) throws IOException { |
| 176 | if (((this.getBody()).getSize() > 0) || TagOptionSingleton.getInstance().isLyrics3SaveEmptyField()) { |
| 177 | final byte[] buffer = new byte[3]; |
| 178 | final String str = getIdentifier(); |
| 179 | for (int i = 0; i < str.length(); i++) { |
| 180 | buffer[i] = (byte) str.charAt(i); |
| 181 | } |
| 182 | file.write(buffer, 0, str.length()); |
| 183 | this.getBody().write(file); |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | private AbstractLyrics3v2FieldBody readBody(final String identifier, final RandomAccessFile file) |
| 188 | throws InvalidTagException, IOException { |
| 189 | final AbstractLyrics3v2FieldBody newBody; |
| 190 | if (identifier.equals("AUT")) { |
| 191 | newBody = new FieldBodyAUT(file); |
| 192 | } else if (identifier.equals("EAL")) { |
| 193 | newBody = new FieldBodyEAL(file); |
| 194 | } else if (identifier.equals("EAR")) { |
| 195 | newBody = new FieldBodyEAR(file); |
| 196 | } else if (identifier.equals("ETT")) { |
| 197 | newBody = new FieldBodyETT(file); |
| 198 | } else if (identifier.equals("IMG")) { |
| 199 | newBody = new FieldBodyIMG(file); |
| 200 | } else if (identifier.equals("IND")) { |
| 201 | newBody = new FieldBodyIND(file); |
| 202 | } else if (identifier.equals("INF")) { |
| 203 | newBody = new FieldBodyINF(file); |
| 204 | } else if (identifier.equals("LYR")) { |
| 205 | newBody = new FieldBodyLYR(file); |
| 206 | } else { |
| 207 | newBody = new FieldBodyUnsupported(file); |
| 208 | } |
| 209 | return newBody; |
| 210 | } |
| 211 | } |