package org.farng.mp3.id3; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Iterator; import org.farng.mp3.AbstractMP3Tag; import org.farng.mp3.TagException; import org.farng.mp3.TagNotFoundException; import org.farng.mp3.TagOptionSingleton; import org.farng.mp3.TagUtility; /** * <TABLE border=0> <TBODY> <TR> <TD class=h2>What is ID3v1.1?</TD></TR></TBODY></TABLE> <TABLE border=0> <TBODY> <TR * vAlign=top> <TD> <P>ID3v1 may well be easy to implement for programmers, but it sure is frustrating for those with * their own, creative ideas. Since the ID3v1 tag had a fixed size and no space marked "Reserved for future use", there * isn't really room for that much improvement, if you want to maintain compatibility with existing software.</P> * <p/> * <P>One who found a way out was Michael Mutschler who made a quite clever improvement on ID3v1. Since all non-filled * fields must be padded with zeroed bytes its a good assumption that all ID3v1 readers will stop reading the field when * they encounter a zeroed byte. If the second last byte of a field is zeroed and the last one isn't we have an extra * byte to fill with information. As the comments field is to short to write anything useful in the ID3v1.1 standard * declares that this field should be 28 characters, that the next byte always should be zero and that the last byte * before the genre byte should contain which track on the CD this music comes from.</P></TD> </TR></TBODY></TABLE> * * @author Eric Farng * @version $Revision: 2374 $ */ public class ID3v1_1 extends ID3v1 { protected byte track = -1; /** * Creates a new ID3v1_1 object. */ public ID3v1_1() { super(); } /** * Creates a new ID3v1_1 object. */ public ID3v1_1(final ID3v1_1 copyObject) { super(copyObject); this.track = copyObject.track; } /** * Creates a new ID3v1_1 object. */ public ID3v1_1(final AbstractMP3Tag mp3tag) { if (mp3tag != null) { if (mp3tag instanceof ID3v1) { if (mp3tag instanceof ID3v1_1) { throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument"); } // id3v1_1 objects are also id3v1 objects final ID3v1 id3old = (ID3v1) mp3tag; this.title = new String(id3old.title.trim()); this.artist = new String(id3old.artist.trim()); this.album = new String(id3old.album.trim()); this.comment = new String(id3old.comment.trim()); this.year = new String(id3old.year.trim()); this.genre = id3old.genre; } else { // first change the tag to ID3v2_4 tag. // id3v2_4 can take any tag. final ID3v2_4 id3tag; id3tag = new ID3v2_4(mp3tag); ID3v2_4Frame frame; String text; if (id3tag.hasFrame("TIT2")) { frame = (ID3v2_4Frame) id3tag.getFrame("TIT2"); text = ((FrameBodyTIT2) frame.getBody()).getText(); this.title = TagUtility.truncate(text, 30); } if (id3tag.hasFrame("TPE1")) { frame = (ID3v2_4Frame) id3tag.getFrame("TPE1"); text = ((FrameBodyTPE1) frame.getBody()).getText(); this.artist = TagUtility.truncate(text, 30); } if (id3tag.hasFrame("TALB")) { frame = (ID3v2_4Frame) id3tag.getFrame("TALB"); text = ((FrameBodyTALB) frame.getBody()).getText(); this.album = TagUtility.truncate(text, 30); } if (id3tag.hasFrame("TDRC")) { frame = (ID3v2_4Frame) id3tag.getFrame("TDRC"); text = ((FrameBodyTDRC) frame.getBody()).getText(); this.year = TagUtility.truncate(text, 4); } if (id3tag.hasFrameOfType("COMM")) { final Iterator iterator = id3tag.getFrameOfType("COMM"); text = ""; while (iterator.hasNext()) { frame = (ID3v2_4Frame) iterator.next(); text += (((FrameBodyCOMM) frame.getBody()).getText() + " "); } this.comment = TagUtility.truncate(text, 28); } if (id3tag.hasFrame("TCON")) { frame = (ID3v2_4Frame) id3tag.getFrame("TCON"); text = ((FrameBodyTCON) frame.getBody()).getText(); try { this.genre = (byte) TagUtility.findNumber(text); } catch (TagException ex) { this.genre = 0; } } if (id3tag.hasFrame("TRCK")) { frame = (ID3v2_4Frame) id3tag.getFrame("TRCK"); text = ((FrameBodyTRCK) frame.getBody()).getText(); try { this.track = (byte) TagUtility.findNumber(text); } catch (TagException ex) { this.track = 0; } } } } } /** * Creates a new ID3v1_1 object. */ public ID3v1_1(final RandomAccessFile file) throws TagNotFoundException, IOException { this.read(file); } public void setComment(final String comment) { this.comment = TagUtility.truncate(comment, 28); } public String getComment() { return this.comment; } public String getIdentifier() { return "ID3v1_1.10"; } public void setTrack(final byte track) { this.track = track; } public byte getTrack() { return this.track; } public void append(final AbstractMP3Tag tag) { final ID3v1_1 oldTag = this; final ID3v1_1 newTag; if (tag != null) { if (tag instanceof ID3v1_1) { newTag = (ID3v1_1) tag; } else { newTag = new ID3v1_1(tag); } if (tag instanceof org.farng.mp3.lyrics3.AbstractLyrics3) { TagOptionSingleton.getInstance().setId3v1SaveTrack(false); } oldTag.track = (TagOptionSingleton.getInstance().isId3v1SaveTrack() && (oldTag.track <= 0)) ? newTag.track : oldTag.track; // we don't need to reset the tag options because // we want to save all fields (default) } // we can't send newTag here because we need to keep the lyrics3 // class type ... check super.append and you'll see what i mean. super.append(tag); } public boolean equals(final Object obj) { if ((obj instanceof ID3v1_1) == false) { return false; } final ID3v1_1 id3v1_1 = (ID3v1_1) obj; if (this.track != id3v1_1.track) { return false; } return super.equals(obj); } public void overwrite(final AbstractMP3Tag tag) { final ID3v1_1 oldTag = this; ID3v1_1 newTag = null; if (tag != null) { if (tag instanceof ID3v1_1) { newTag = (ID3v1_1) tag; } else { newTag = new ID3v1_1(tag); } if (tag instanceof org.farng.mp3.lyrics3.AbstractLyrics3) { TagOptionSingleton.getInstance().setId3v1SaveTrack(false); } oldTag.track = TagOptionSingleton.getInstance().isId3v1SaveTrack() ? newTag.track : oldTag.track; // we don't need to reset the tag options because // we want to save all fields (default) } super.overwrite(newTag); } public void read(final RandomAccessFile file) throws TagNotFoundException, IOException { final byte[] buffer = new byte[30]; if (this.seek(file) == false) { throw new TagNotFoundException("ID3v1.1 tag not found"); } file.read(buffer, 0, 30); this.title = new String(buffer, 0, 30, "ISO-8859-1").trim(); file.read(buffer, 0, 30); this.artist = new String(buffer, 0, 30, "ISO-8859-1").trim(); file.read(buffer, 0, 30); this.album = new String(buffer, 0, 30, "ISO-8859-1").trim(); file.read(buffer, 0, 4); this.year = new String(buffer, 0, 4, "ISO-8859-1").trim(); file.read(buffer, 0, 28); this.comment = new String(buffer, 0, 28, "ISO-8859-1").trim(); // if this value is zero, then check the next value // to see if it's the track number. ID3v1.1 file.read(buffer, 0, 2); if (buffer[0] == 0) { this.track = buffer[1]; } else { throw new TagNotFoundException("ID3v1.1 Tag Not found"); } file.read(buffer, 0, 1); this.genre = buffer[0]; } public boolean seek(final RandomAccessFile file) throws IOException { final byte[] buffer = new byte[3]; if (file.length() < 128) { return false; } // Check for the empty byte before the TRACK file.seek(file.length() - 3); buffer[0] = file.readByte(); if (buffer[0] != 0) { return false; } // If there's a tag, it's 128 bytes long and we'll find the tag file.seek(file.length() - 128); // read the TAG value file.read(buffer, 0, 3); final String tag = new String(buffer, 0, 3); return tag.equals("TAG"); } public String toString() { String str = getIdentifier() + " " + this.getSize() + "\n"; str += ("Title = " + this.title + "\n"); str += ("Artist = " + this.artist + "\n"); str += ("Album = " + this.album + "\n"); str += ("Comment = " + this.comment + "\n"); str += ("Year = " + this.year + "\n"); str += ("Genre = " + this.genre + "\n"); str += ("Track = " + this.track + "\n"); return str; } public void write(final AbstractMP3Tag tag) { final ID3v1_1 oldTag = this; ID3v1_1 newTag = null; if (tag != null) { if (tag instanceof ID3v1_1) { newTag = (ID3v1_1) tag; } else { newTag = new ID3v1_1(tag); } oldTag.track = newTag.track; } super.write(newTag); } public void write(final RandomAccessFile file) throws IOException { final byte[] buffer = new byte[128]; int i; int offset = 3; String str; delete(file); file.seek(file.length()); buffer[0] = (byte) 'T'; buffer[1] = (byte) 'A'; buffer[2] = (byte) 'G'; str = TagUtility.truncate(this.title, 30); for (i = 0; i < str.length(); i++) { buffer[i + offset] = (byte) str.charAt(i); } offset += 30; str = TagUtility.truncate(this.artist, 30); for (i = 0; i < str.length(); i++) { buffer[i + offset] = (byte) str.charAt(i); } offset += 30; str = TagUtility.truncate(this.album, 30); for (i = 0; i < str.length(); i++) { buffer[i + offset] = (byte) str.charAt(i); } offset += 30; str = TagUtility.truncate(this.year, 4); for (i = 0; i < str.length(); i++) { buffer[i + offset] = (byte) str.charAt(i); } offset += 4; str = TagUtility.truncate(this.comment, 28); for (i = 0; i < str.length(); i++) { buffer[i + offset] = (byte) str.charAt(i); } offset += 28; offset++; buffer[offset] = this.track; // skip one byte extra blank for 1.1 // definition offset++; buffer[offset] = this.genre; file.write(buffer); } public String getTrackNumberOnAlbum() { return Integer.toString(getTrack()); } public void setTrackNumberOnAlbum(String trackNumberOnAlbum) { setTrack(Byte.parseByte(trackNumberOnAlbum.trim())); } }