EMMA Coverage Report (generated Tue Mar 14 21:50:42 EST 2006)
[all classes][org.farng.mp3.id3]

COVERAGE SUMMARY FOR SOURCE FILE [ID3v2_2.java]

nameclass, %method, %block, %line, %
ID3v2_2.java100% (1/1)84%  (27/32)61%  (610/994)63%  (152.2/240)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ID3v2_2100% (1/1)84%  (27/32)61%  (610/994)63%  (152.2/240)
ID3v2_2 (AbstractMP3Tag): void 0%   (0/1)0%   (0/64)0%   (0/18)
ID3v2_2 (RandomAccessFile): void 0%   (0/1)0%   (0/12)0%   (0/5)
overwrite (AbstractMP3Tag): void 0%   (0/1)0%   (0/17)0%   (0/5)
write (AbstractMP3Tag): void 0%   (0/1)0%   (0/17)0%   (0/5)
write (RandomAccessFile): void 0%   (0/1)0%   (0/89)0%   (0/21)
read (RandomAccessFile): void 100% (1/1)8%   (12/147)10%  (3/30)
seek (RandomAccessFile): boolean 100% (1/1)50%  (25/50)67%  (6/9)
equals (Object): boolean 100% (1/1)77%  (20/26)62%  (5/8)
setSongComment (String): void 100% (1/1)79%  (23/29)83%  (5/6)
setSongLyric (String): void 100% (1/1)79%  (23/29)83%  (5/6)
getSongLyric (): String 100% (1/1)79%  (27/34)82%  (9/11)
ID3v2_2 (): void 100% (1/1)100% (15/15)100% (6/6)
ID3v2_2 (ID3v2_2): void 100% (1/1)100% (18/18)100% (6/6)
append (AbstractMP3Tag): void 100% (1/1)100% (17/17)100% (5/5)
getAlbumTitle (): String 100% (1/1)100% (18/18)100% (6/6)
getAuthorComposer (): String 100% (1/1)100% (18/18)100% (6/6)
getIdentifier (): String 100% (1/1)100% (2/2)100% (1/1)
getLeadArtist (): String 100% (1/1)100% (18/18)100% (6/6)
getSize (): int 100% (1/1)100% (20/20)100% (6/6)
getSongComment (): String 100% (1/1)100% (18/18)100% (6/6)
getSongGenre (): String 100% (1/1)100% (18/18)100% (6/6)
getSongTitle (): String 100% (1/1)100% (18/18)100% (6/6)
getTrackNumberOnAlbum (): String 100% (1/1)100% (18/18)100% (6/6)
getYearReleased (): String 100% (1/1)100% (18/18)100% (6/6)
setAlbumTitle (String): void 100% (1/1)100% (27/27)100% (6/6)
setAuthorComposer (String): void 100% (1/1)100% (27/27)100% (6/6)
setLeadArtist (String): void 100% (1/1)100% (27/27)100% (6/6)
setSongGenre (String): void 100% (1/1)100% (27/27)100% (6/6)
setSongTitle (String): void 100% (1/1)100% (27/27)100% (6/6)
setTrackNumberOnAlbum (String): void 100% (1/1)100% (27/27)100% (6/6)
setYearReleased (String): void 100% (1/1)100% (27/27)100% (6/6)
toString (): String 100% (1/1)100% (75/75)100% (8/8)

1package org.farng.mp3.id3;
2 
3import org.farng.mp3.AbstractMP3Tag;
4import org.farng.mp3.InvalidTagException;
5import org.farng.mp3.MP3File;
6import org.farng.mp3.TagConstant;
7import org.farng.mp3.TagException;
8import org.farng.mp3.TagNotFoundException;
9 
10import java.io.IOException;
11import java.io.RandomAccessFile;
12import 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>&nbsp;</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> &nbsp;&nbsp;&nbsp;&nbsp;$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 */
93public 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}

[all classes][org.farng.mp3.id3]
EMMA 2.0.5312 (C) Vladimir Roubtsov