Package org.farng.mp3.lyrics3

Source Code of org.farng.mp3.lyrics3.Lyrics3v2

package org.farng.mp3.lyrics3;

import org.farng.mp3.AbstractMP3Tag;
import org.farng.mp3.InvalidTagException;
import org.farng.mp3.TagException;
import org.farng.mp3.TagNotFoundException;
import org.farng.mp3.TagOptionSingleton;
import org.farng.mp3.id3.AbstractID3v2Frame;
import org.farng.mp3.id3.ID3v1;
import org.farng.mp3.id3.ID3v2_4;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
* <TABLE border=0> <TBODY> <TR> <TD class=h2>What is Lyrics3 v2.00?</TD></TR></TBODY></TABLE> <TABLE border=0> <TBODY>
* <TR vAlign=top> <TD> <P>The Lyrics3 v2.00 tag is more complicated than the previous Lyrics3 tag but has a lot more
* capabilities. Just like the old Lyrics3 tag it resides between the audio and the ID3 tag, which must be present. The
* tag uses only text for everything from content to size descriptors, which are represented as numerical strings. This
* makes it easier to implement a Lyrics3 v2.00 reader. At least if BASIC is your language of choice.</P>
* <p/>
* <P>The Lyrics3 block, after the MP3 audio and before the ID3 tag, begins with the word "LYRICSBEGIN" after which a
* number of field records follows. The Lyrics3 block ends with a six character size descriptor and the string
* "LYRICS200". The size value includes the "LYRICSBEGIN" and "LYRICS200" strings.</P>
* <p/>
* <P>Lyrics2 v2.00 uses somthing called fields to represent information. The data in a field can consist of ASCII
* characters in the range 01 to 254 according to the standard. As the ASCII character map is only defined from 00 to
* 128 ISO-8859-1 might be assumed. Numerical fields are 5 or 6 characters long, depending on location, and are padded
* with zeroes.
* <p/>
* <P>Only the size of the tag sets the limit for how many fields may be present. All fields uses a simple structure
* that includes a three character field ID, six characters describing the size of the information and the actual
* information. This makes it possible to read unknown fields and write them back when saving the tag. There are no
* required fields in the tag, but at least one field must exist. Fields can appear in any order in the tag, except the
* indication field which must be the first field if used. Fields that include more then one line uses [CR][LF]
* delimiters between lines.</P></TD> </TR></TBODY></TABLE> <BR>&nbsp; <TABLE border=0> <TBODY> <TR> <TD
* class=h2>Defined fields</TD></TR></TBODY></TABLE> <TABLE border=0> <TBODY> <TR vAlign=top> <TD> <P>The following list
* is a list of currently defined field IDs. More fields might be added if needed on newer versions of the Lyrics3 v2.00
* specifications. Unknown fields should be ignored.</P> <TABLE> <TBODY> <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> <TD>00002</TD> <TD>Indications
* field. This is always two characters big in v2.00, but might be bigger in a future standard. The first byte indicates
* wether or not a lyrics field is present. "1" for present and "0" for otherwise. The second character indicates if
* there is a timestamp in the lyrics. Again "1" for yes and "0" for no.</TD></TR> <TR vAlign=top> <TD><B>LYR</B></TD>
* <TD>99999</TD> <TD>Lyrics multi line text. Timestamps can be used anywhere in the text in any order. Timestamp format
* is [mm:ss] (no spaces allowed in the timestamps).</TD></TR> <TR vAlign=top> <TD><B>INF</B></TD> <TD>99999</TD>
* <TD>Additional information multi line text.</TD></TR> <TR vAlign=top> <TD><B>AUT</B></TD> <TD>00250</TD>
* <TD>Lyrics/Music Author name.</TD></TR> <TR vAlign=top> <TD><B>EAL</B></TD> <TD>00250</TD> <TD>Extended Album
* name.</TD></TR> <TR vAlign=top> <TD><B>EAR</B></TD> <TD>00250</TD> <TD>Extended Artist name.</TD></TR> <TR
* vAlign=top> <TD><B>ETT</B></TD> <TD>00250</TD> <TD>Extended Track Title.</TD></TR> <TR vAlign=top>
* <TD><B>IMG</B></TD> <TD>99999</TD> <TD>Link to an image files (BMP or JPG format). Image lines include filename,
* description and timestamp separated by delimiter - two ASCII chars 124 ("||"). Description and timestamp are
* optional, but if timestamp is used, and there is no description, two delimiters ("||||") should be used between the
* filename and the timestamp. Multiple images are allowed by using a [CR][LF] delimiter between each image line. No
* [CR][LF] is needed after the last image line. Number of images is not limited (except by the field
* size).<BR><B>Filename</B> can be in one of these formats: <UL> <LI>Filename only - when the image is located in the
* same path as the MP3 file (preferred, since if you move the mp3 file this will still be correct) <LI>Relative Path +
* Filename - when the image is located in a subdirectory below the MP3 file (i.e. images\cover.jpg) <LI>Full path +
* Filename - when the image is located in a totally different path or drive. This will not work if the image is moved
* or drive letters has changed, and so should be avoided if possible (i.e. c:\images\artist.jpg)</LI></UL><B>Description</B>
* can be up to 250 chars long.<BR><B>Timestamp</B> must be formatted like the lyrics timestamp which is "[mm:ss]". If
* an image has a timestamp, then the visible image will automatically switch to that image on the timestamp play time,
* just the same as the selected lyrics line is switched based on timestamps.</TD></TR></TBODY></TABLE>
* </TD></TR></TBODY></TABLE> &nbsp; <TABLE border=0> <TBODY> <TR vAlign=top> <TD> <P>The extended Album, Artist and
* Track are an extension to the fields in the ID3v1 tag - which are limited to 30 chars. If these extended fields
* exist, make sure their first 30 chars are exactly the same as the ones in the ID3v1 tag. If they are the same,
* display the extended field. If not, display the one from the ID tag. These 'mismatched' extended fields, should be
* removed when saving the lyrics tag.</P>
* <p/>
* <P>When saving the extended fields, make sure to copy the first 30 chars of each field to the ID3 tag matching
* fields. It is recommended NOT to save extended fields at all, if they are not larger then 30
* chars.</P></TD></TR></TBODY></TABLE>
*
* @author Eric Farng
* @version $Revision: 1.5 $
*/
public class Lyrics3v2 extends AbstractLyrics3 {

    private Map fieldMap = new HashMap(8);

    /**
     * Creates a new Lyrics3v2 object.
     */
    public Lyrics3v2() {
        super();
    }

    /**
     * Creates a new Lyrics3v2 object.
     */
    public Lyrics3v2(final Lyrics3v2 copyObject) {
        super(copyObject);
        final Iterator iterator = copyObject.fieldMap.keySet().iterator();
        String oldIdentifier;
        String newIdentifier;
        Lyrics3v2Field newObject;
        while (iterator.hasNext()) {
            oldIdentifier = iterator.next().toString();
            newIdentifier = oldIdentifier;
            newObject = new Lyrics3v2Field((Lyrics3v2Field) copyObject.fieldMap.get(newIdentifier));
            fieldMap.put(newIdentifier, newObject);
        }
    }

    /**
     * Creates a new Lyrics3v2 object.
     */
    public Lyrics3v2(final AbstractMP3Tag mp3tag) {
        super();
        if (mp3tag != null) {
            // upgrade the tag to lyrics3v2
            if (mp3tag instanceof Lyrics3v2) {
                throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument");
            } else if (mp3tag instanceof Lyrics3v1) {
                final Lyrics3v1 lyricOld = (Lyrics3v1) mp3tag;
                final Lyrics3v2Field newField = new Lyrics3v2Field(new FieldBodyLYR(lyricOld.getLyric()));
                fieldMap.put(newField.getIdentifier(), newField);
            } else {
                Lyrics3v2Field newField;
                final Iterator iterator;
                iterator = (new ID3v2_4(mp3tag)).iterator();
                while (iterator.hasNext()) {
                    try {
                        newField = new Lyrics3v2Field((AbstractID3v2Frame) iterator.next());
                        this.fieldMap.put(newField.getIdentifier(), newField);
                    } catch (TagException ex) {
                        //invalid frame to create lyrics3 field. ignore and
                        // keep going
                    }
                }
            }
        }
    }

    /**
     * Creates a new Lyrics3v2 object.
     */
    public Lyrics3v2(final RandomAccessFile file) throws TagNotFoundException, IOException {
        this.read(file);
    }

    public void setField(final Lyrics3v2Field field) {
        if (field.getBody() != null) {
            this.fieldMap.put(field.getIdentifier(), field);
        }
    }

    /**
     * Gets the value of the frame identified by identifier
     *
     * @param identifier The three letter code
     *
     * @return The value associated with the identifier
     */
    public Lyrics3v2Field getField(final String identifier) {
        return (Lyrics3v2Field) this.fieldMap.get(identifier);
    }

    public int getFieldCount() {
        return this.fieldMap.size();
    }

    public String getIdentifier() {
        return "Lyrics3v2.00";
    }

    public int getSize() {
        int size = 0;
        final Iterator iterator = this.fieldMap.values().iterator();
        Lyrics3v2Field field;
        while (iterator.hasNext()) {
            field = (Lyrics3v2Field) iterator.next();
            size += field.getSize();
        }

        // include LYRICSBEGIN, but not 6 char size or LYRICSEND
        return 11 + size;
    }

    public void append(final AbstractMP3Tag tag) {
        final Lyrics3v2 oldTag = this;
        final Lyrics3v2 newTag;
        if (tag != null) {
            if (tag instanceof Lyrics3v2) {
                newTag = (Lyrics3v2) tag;
            } else {
                newTag = new Lyrics3v2(tag);
            }
            Iterator iterator = newTag.fieldMap.values().iterator();
            Lyrics3v2Field field;
            AbstractLyrics3v2FieldBody body;
            while (iterator.hasNext()) {
                field = (Lyrics3v2Field) iterator.next();
                if (oldTag.hasField(field.getIdentifier()) == false) {
                    oldTag.setField(field);
                } else {
                    body = (AbstractLyrics3v2FieldBody) oldTag.getField(field.getIdentifier()).getBody();
                    final boolean save = TagOptionSingleton.getInstance().getLyrics3SaveField(field.getIdentifier());
                    if ((body.getSize() == 0) && save) {
                        oldTag.setField(field);
                    }
                }
            }

            // reset tag options to save all current fields.
            iterator = oldTag.fieldMap.keySet().iterator();
            String id;
            while (iterator.hasNext()) {
                id = (String) iterator.next();
                TagOptionSingleton.getInstance().setLyrics3SaveField(id, true);
            }
        }
    }

    public boolean equals(final Object obj) {
        if ((obj instanceof Lyrics3v2) == false) {
            return false;
        }
        final Lyrics3v2 lyrics3v2 = (Lyrics3v2) obj;
        if (this.fieldMap.equals(lyrics3v2.fieldMap) == false) {
            return false;
        }
        return super.equals(obj);
    }

    public boolean hasField(final String identifier) {
        return this.fieldMap.containsKey(identifier);
    }

    public Iterator iterator() {
        return this.fieldMap.values().iterator();
    }

    public void overwrite(final AbstractMP3Tag tag) {
        final Lyrics3v2 oldTag = this;
        final Lyrics3v2 newTag;
        if (tag != null) {
            if (tag instanceof Lyrics3v2) {
                newTag = (Lyrics3v2) tag;
            } else {
                newTag = new Lyrics3v2(tag);
            }
            Iterator iterator = newTag.fieldMap.values().iterator();
            Lyrics3v2Field field;
            while (iterator.hasNext()) {
                field = (Lyrics3v2Field) iterator.next();
                if (TagOptionSingleton.getInstance().getLyrics3SaveField(field.getIdentifier())) {
                    oldTag.setField(field);
                }
            }

            // reset tag options to save all current fields.
            iterator = oldTag.fieldMap.keySet().iterator();
            String id;
            while (iterator.hasNext()) {
                id = (String) iterator.next();
                TagOptionSingleton.getInstance().setLyrics3SaveField(id, true);
            }
        }
    }

    public void read(final RandomAccessFile file) throws TagNotFoundException, IOException {
        final long filePointer;
        final int lyricSize;
        if (seek(file)) {
            lyricSize = seekSize(file);
        } else {
            throw new TagNotFoundException("Lyrics3v2.00 Tag Not Found");
        }

        // reset file pointer to the beginning of the tag;
        seek(file);
        filePointer = file.getFilePointer();
        this.fieldMap = new HashMap();
        Lyrics3v2Field lyric;

        // read each of the fields
        while ((file.getFilePointer() - filePointer) < (lyricSize - 11)) {
            try {
                lyric = new Lyrics3v2Field(file);
                setField(lyric);
            } catch (InvalidTagException ex) {
                // keep reading until we're done
            }
        }
    }

    public void removeField(final String identifier) {
        this.fieldMap.remove(identifier);
    }

    public boolean seek(final RandomAccessFile file) throws IOException {
        final byte[] buffer = new byte[11];
        String lyricEnd;
        final String lyricStart;
        long filePointer;
        final long lyricSize;

        // check right before the ID3 1.0 tag for the lyrics tag
        file.seek(file.length() - 128 - 9);
        file.read(buffer, 0, 9);
        lyricEnd = new String(buffer, 0, 9);
        if (lyricEnd.equals("LYRICS200")) {
            filePointer = file.getFilePointer();
        } else {
            // check the end of the file for a lyrics tag incase an ID3
            // tag wasn't placed after it.
            file.seek(file.length() - 9);
            file.read(buffer, 0, 9);
            lyricEnd = new String(buffer, 0, 9);
            if (lyricEnd.equals("LYRICS200")) {
                filePointer = file.getFilePointer();
            } else {
                return false;
            }
        }

        // read the 6 bytes for the length of the tag
        filePointer -= (9 + 6);
        file.seek(filePointer);
        file.read(buffer, 0, 6);
        lyricSize = Integer.parseInt(new String(buffer, 0, 6));

        // read the lyrics begin tag if it exists.
        file.seek(filePointer - lyricSize);
        file.read(buffer, 0, 11);
        lyricStart = new String(buffer, 0, 11);
        return lyricStart.equals("LYRICSBEGIN") == true;
    }

    public String toString() {
        final Iterator iterator = this.fieldMap.values().iterator();
        Lyrics3v2Field field;
        String str = getIdentifier() + " " + this.getSize() + "\n";
        while (iterator.hasNext()) {
            field = (Lyrics3v2Field) iterator.next();
            str += (field.toString() + "\n");
        }
        return str;
    }

    public void updateField(final String identifier) {
        Lyrics3v2Field lyrField;
        if (identifier.equals("IND")) {
            final boolean lyricsPresent = this.fieldMap.containsKey("LYR");
            boolean timeStampPresent = false;
            if (lyricsPresent) {
                lyrField = (Lyrics3v2Field) this.fieldMap.get("LYR");
                final FieldBodyLYR lyrBody = (FieldBodyLYR) lyrField.getBody();
                timeStampPresent = lyrBody.hasTimeStamp();
            }
            lyrField = new Lyrics3v2Field(new FieldBodyIND(lyricsPresent, timeStampPresent));
            setField(lyrField);
        }
    }

    public void write(final AbstractMP3Tag tag) {
        final Lyrics3v2 oldTag = this;
        final Lyrics3v2 newTag;
        if (tag != null) {
            if (tag instanceof Lyrics3v2) {
                newTag = (Lyrics3v2) tag;
            } else {
                newTag = new Lyrics3v2(tag);
            }
            final Iterator iterator = newTag.fieldMap.values().iterator();
            Lyrics3v2Field field;
            oldTag.fieldMap.clear();
            while (iterator.hasNext()) {
                field = (Lyrics3v2Field) iterator.next();
                oldTag.setField(field);
            }
        }
    }

    public void write(final RandomAccessFile file) throws IOException {
        int offset = 0;
        final long filePointer;
        final byte[] buffer = new byte[6 + 9];
        String str;
        Lyrics3v2Field field;
        final Iterator iterator;
        ID3v1 id3v1tag = new ID3v1();
        id3v1tag = id3v1tag.getID3tag(file);
        delete(file);
        file.seek(file.length());
        filePointer = file.getFilePointer();
        str = "LYRICSBEGIN";
        for (int i = 0; i < str.length(); i++) {
            buffer[i] = (byte) str.charAt(i);
        }
        file.write(buffer, 0, str.length());

        // IND needs to go first. lets create/update it and write it first.
        updateField("IND");
        field = (Lyrics3v2Field) this.fieldMap.get("IND");
        field.write(file);
        iterator = this.fieldMap.values().iterator();
        while (iterator.hasNext()) {
            field = (Lyrics3v2Field) iterator.next();
            final String id = field.getIdentifier();
            final boolean save = TagOptionSingleton.getInstance().getLyrics3SaveField(id);
            if ((id.equals("IND") == false) && save) {
                field.write(file);
            }
        }
        final long size;
        size = file.getFilePointer() - filePointer;
        str = Long.toString(size);
        for (int i = 0; i < (6 - str.length()); i++) {
            buffer[i] = (byte) '0';
        }
        offset += (6 - str.length());
        for (int i = 0; i < str.length(); i++) {
            buffer[i + offset] = (byte) str.charAt(i);
        }
        offset += str.length();
        str = "LYRICS200";
        for (int i = 0; i < str.length(); i++) {
            buffer[i + offset] = (byte) str.charAt(i);
        }
        offset += str.length();
        file.write(buffer, 0, offset);
        if (id3v1tag != null) {
            id3v1tag.write(file);
        }
    }

    private int seekSize(final RandomAccessFile file) throws IOException {
        final byte[] buffer = new byte[11];
        String lyricEnd;
        long filePointer;

        // check right before the ID3 1.0 tag for the lyrics tag
        file.seek(file.length() - 128 - 9);
        file.read(buffer, 0, 9);
        lyricEnd = new String(buffer, 0, 9);
        if (lyricEnd.equals("LYRICS200")) {
            filePointer = file.getFilePointer();
        } else {
            // check the end of the file for a lyrics tag incase an ID3
            // tag wasn't placed after it.
            file.seek(file.length() - 9);
            file.read(buffer, 0, 9);
            lyricEnd = new String(buffer, 0, 9);
            if (lyricEnd.equals("LYRICS200")) {
                filePointer = file.getFilePointer();
            } else {
                return -1;
            }
        }

        // read the 6 bytes for the length of the tag
        filePointer -= (9 + 6);
        file.seek(filePointer);
        file.read(buffer, 0, 6);
        return Integer.parseInt(new String(buffer, 0, 6));
    }

    public String getSongTitle() {
        String title = "";
        Lyrics3v2Field field = getField("ETT");
        if (field != null) {
            FieldBodyETT body = (FieldBodyETT) field.getBody();
            title = body.getTitle();
        }
        return title.trim();
    }

    public String getLeadArtist() {
        String artist = "";
        Lyrics3v2Field field = getField("EAR");
        if (field != null) {
            FieldBodyEAR body = (FieldBodyEAR) field.getBody();
            artist = body.getArtist();
        }
        return artist.trim();
    }

    public String getAlbumTitle() {
        String album = "";
        Lyrics3v2Field field = getField("EAL");
        if (field != null) {
            FieldBodyEAL body = (FieldBodyEAL) field.getBody();
            album = body.getAlbum();
        }
        return album.trim();
    }

    public String getYearReleased() {
        throw new UnsupportedOperationException("This tag does not contain that information");
    }

    public String getSongComment() {
        String additionalInformation = "";
        Lyrics3v2Field field = getField("INF");
        if (field != null) {
            FieldBodyINF body = (FieldBodyINF) field.getBody();
            additionalInformation = body.getAdditionalInformation();
        }
        return additionalInformation.trim();
    }

    public String getSongGenre() {
        throw new UnsupportedOperationException("This tag does not contain that information");
    }

    public String getTrackNumberOnAlbum() {
        throw new UnsupportedOperationException("This tag does not contain that information");
    }

    public String getSongLyric() {
        String lyrics = "";
        Lyrics3v2Field field = getField("LYR");
        if (field != null) {
            FieldBodyLYR body = (FieldBodyLYR) field.getBody();
            lyrics = body.getLyric();
        }
        return lyrics.trim().trim();
    }

    public String getAuthorComposer() {
        String author = "";
        Lyrics3v2Field field = getField("AUT");
        if (field != null) {
            FieldBodyAUT body = (FieldBodyAUT) field.getBody();
            author = body.getAuthor();
        }
        return author.trim();
    }

    public void setSongTitle(String songTitle) {
        Lyrics3v2Field field = getField("ETT");
        if (field == null) {
            field = new Lyrics3v2Field(new FieldBodyETT(songTitle.trim()));
            setField(field);
        } else {
            ((FieldBodyETT) field.getBody()).setTitle(songTitle.trim());
        }
    }

    public void setLeadArtist(String leadArtist) {
        Lyrics3v2Field field = getField("EAR");
        if (field == null) {
            field = new Lyrics3v2Field(new FieldBodyEAR(leadArtist.trim()));
            setField(field);
        } else {
            ((FieldBodyEAR) field.getBody()).setArtist(leadArtist.trim());
        }
    }

    public void setAlbumTitle(String albumTitle) {
        Lyrics3v2Field field = getField("EAL");
        if (field == null) {
            field = new Lyrics3v2Field(new FieldBodyEAL(albumTitle.trim()));
            setField(field);
        } else {
            ((FieldBodyEAL) field.getBody()).setAlbum(albumTitle.trim());
        }
    }

    public void setYearReleased(String yearReleased) {
        throw new UnsupportedOperationException("This tag does not contain that information");
    }

    public void setSongComment(String songComment) {
        Lyrics3v2Field field = getField("INF");
        if (field == null) {
            field = new Lyrics3v2Field(new FieldBodyINF(songComment.trim()));
            setField(field);
        } else {
            ((FieldBodyINF) field.getBody()).setAdditionalInformation(songComment.trim());
        }
    }

    public void setSongGenre(String songGenre) {
        throw new UnsupportedOperationException("This tag does not contain that information");
    }

    public void setTrackNumberOnAlbum(String trackNumberOnAlbum) {
        throw new UnsupportedOperationException("This tag does not contain that information");
    }

    public void setSongLyric(String songLyrics) {
        Lyrics3v2Field field = getField("LYR");
        if (field == null) {
            field = new Lyrics3v2Field(new FieldBodyLYR(songLyrics.trim()));
            setField(field);
        } else {
            ((FieldBodyLYR) field.getBody()).setLyric(songLyrics.trim());
        }
    }

    public void setAuthorComposer(String authorComposer) {
        Lyrics3v2Field field = getField("AUT");
        if (field == null) {
            field = new Lyrics3v2Field(new FieldBodyAUT(authorComposer.trim()));
            setField(field);
        } else {
            ((FieldBodyAUT) field.getBody()).setAuthor(authorComposer.trim());
        }
    }
}
TOP

Related Classes of org.farng.mp3.lyrics3.Lyrics3v2

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.