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