📄 abstractid3v2tag.java
字号:
/*
* MusicTag Copyright (C)2003,2004
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation; either version 2.1 of the License,
* or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library; if not,
* you can get a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.hadeslee.audiotag.tag.id3;
import com.hadeslee.audiotag.audio.mp3.MP3File;
import com.hadeslee.audiotag.audio.generic.Utils;
import com.hadeslee.audiotag.tag.FieldDataInvalidException;
import com.hadeslee.audiotag.tag.KeyNotFoundException;
import com.hadeslee.audiotag.tag.Tag;
import com.hadeslee.audiotag.tag.TagField;
import com.hadeslee.audiotag.tag.TagFieldKey;
import com.hadeslee.audiotag.tag.id3.valuepair.PictureTypes;
import com.hadeslee.audiotag.tag.id3.valuepair.ImageFormats;
import com.hadeslee.audiotag.tag.id3.valuepair.TextEncoding;
import com.hadeslee.audiotag.tag.datatype.DataTypes;
import com.hadeslee.audiotag.tag.id3.framebody.AbstractFrameBodyTextInfo;
import com.hadeslee.audiotag.tag.id3.framebody.AbstractFrameBodyUrlLink;
import com.hadeslee.audiotag.tag.id3.framebody.FrameBodyAPIC;
import com.hadeslee.audiotag.tag.id3.framebody.FrameBodyCOMM;
import com.hadeslee.audiotag.tag.id3.framebody.FrameBodyPIC;
import com.hadeslee.audiotag.tag.id3.framebody.FrameBodyTDRC;
import com.hadeslee.audiotag.tag.id3.framebody.FrameBodyTXXX;
import com.hadeslee.audiotag.tag.id3.framebody.FrameBodyUFID;
import com.hadeslee.audiotag.tag.id3.framebody.FrameBodyUnsupported;
import com.hadeslee.audiotag.tag.id3.framebody.FrameBodyWXXX;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.util.*;
/**
* This is the abstract base class for all ID3v2 tags.
*
* @author : Paul Taylor
* @author : Eric Farng
* @version $Id: AbstractID3v2Tag.java,v 1.30 2007/12/03 13:28:04 paultaylor Exp $
*/
public abstract class AbstractID3v2Tag extends AbstractID3Tag implements Tag
{
protected static final String TYPE_HEADER = "header";
protected static final String TYPE_BODY = "body";
//Tag ID as held in file
protected static final byte[] TAG_ID = {'I', 'D', '3'};
//The tag header is the same for ID3v2 versions
public static final int TAG_HEADER_LENGTH = 10;
protected static final int FIELD_TAGID_LENGTH = 3;
protected static final int FIELD_TAG_MAJOR_VERSION_LENGTH = 1;
protected static final int FIELD_TAG_MINOR_VERSION_LENGTH = 1;
protected static final int FIELD_TAG_FLAG_LENGTH = 1;
protected static final int FIELD_TAG_SIZE_LENGTH = 4;
protected static final int FIELD_TAGID_POS = 0;
protected static final int FIELD_TAG_MAJOR_VERSION_POS = 3;
protected static final int FIELD_TAG_MINOR_VERSION_POS = 4;
protected static final int FIELD_TAG_FLAG_POS = 5;
protected static final int FIELD_TAG_SIZE_POS = 6;
protected static final int TAG_SIZE_INCREMENT = 100;
//The max size we try to write in one go to avoid out of memory errors (10mb)
private static final long MAXIMUM_WRITABLE_CHUNK_SIZE = 10000000;
/**
* Map of all frames for this tag
*/
public HashMap frameMap = null;
/**
* Holds the ids of invalid duplicate frames
*/
protected static final String TYPE_DUPLICATEFRAMEID = "duplicateFrameId";
protected String duplicateFrameId = "";
/**
* Holds byte count of invalid duplicate frames
*/
protected static final String TYPE_DUPLICATEBYTES = "duplicateBytes";
protected int duplicateBytes = 0;
/**
* Holds byte count of empty frames
*/
protected static final String TYPE_EMPTYFRAMEBYTES = "emptyFrameBytes";
protected int emptyFrameBytes = 0;
/**
* Holds the size of the tag as reported by the tag header
*/
protected static final String TYPE_FILEREADSIZE = "fileReadSize";
protected int fileReadSize = 0;
/**
* Holds byte count of invalid frames
*/
protected static final String TYPE_INVALIDFRAMEBYTES = "invalidFrameBytes";
protected int invalidFrameBytes = 0;
/**
* Empty Constructor
*/
public AbstractID3v2Tag()
{
}
/**
* This constructor is used when a tag is created as a duplicate of another
* tag of the same type and version.
*/
protected AbstractID3v2Tag(AbstractID3v2Tag copyObject)
{
}
/**
* Copy primitives apply to all tags
*/
protected void copyPrimitives(AbstractID3v2Tag copyObject)
{
logger.info("Copying Primitives");
//Primitives type variables common to all IDv2 Tags
this.duplicateFrameId = new String(copyObject.duplicateFrameId);
this.duplicateBytes = copyObject.duplicateBytes;
this.emptyFrameBytes = copyObject.emptyFrameBytes;
this.fileReadSize = copyObject.fileReadSize;
this.invalidFrameBytes = copyObject.invalidFrameBytes;
}
/**
* Copy frames from another tag, needs implemanting by subclasses
*/
protected abstract void copyFrames(AbstractID3v2Tag copyObject);
/**
* Returns the number of bytes which come from duplicate frames
*
* @return the number of bytes which come from duplicate frames
*/
public int getDuplicateBytes()
{
return duplicateBytes;
}
/**
* Return the string which holds the ids of all
* duplicate frames.
*
* @return the string which holds the ids of all duplicate frames.
*/
public String getDuplicateFrameId()
{
return duplicateFrameId;
}
/**
* Returns the number of bytes which come from empty frames
*
* @return the number of bytes which come from empty frames
*/
public int getEmptyFrameBytes()
{
return emptyFrameBytes;
}
/**
* Return byte count of invalid frames
*
* @return byte count of invalid frames
*/
public int getInvalidFrameBytes()
{
return invalidFrameBytes;
}
/**
* Returns the tag size as reported by the tag header
*
* @return the tag size as reported by the tag header
*/
public int getFileReadBytes()
{
return fileReadSize;
}
/**
* Return whether tag has frame with this identifier
* <p/>
* Warning the match is only done against the identifier so if a tag contains a frame with an unsuported body
* but happens to have an identifier that is valid for another version of the tag it will return true
*
* @param identifier frameId to lookup
* @return true if tag has frame with this identifier
*/
public boolean hasFrame(String identifier)
{
return frameMap.containsKey(identifier);
}
/**
* Return whether tag has frame with this identifier and a related body. This is required to protect
* against circumstances whereby a tag contains a frame with an unsupported body
* but happens to have an identifier that is valid for another version of the tag which it has been converted to
* <p/>
* e.g TDRC is an invalid frame in a v23 tag but if somehow a v23tag has been created by another application
* with a TDRC frame we construct an UnsupportedFrameBody to hold it, then this library constructs a
* v24 tag, it will contain a frame with id TDRC but it will not have the expected frame body it is not really a
* TDRC frame.
*
* @param identifier frameId to lookup
* @return true if tag has frame with this identifier
*/
public boolean hasFrameAndBody(String identifier)
{
if (hasFrame(identifier))
{
Object o = getFrame(identifier);
if (o instanceof AbstractID3v2Frame)
{
if (((AbstractID3v2Frame) o).getBody() instanceof FrameBodyUnsupported)
{
return false;
}
return true;
}
return true;
}
return false;
}
/**
* Return whether tag has frame starting with this identifier
* <p/>
* Warning the match is only done against the identifier so if a tag contains a frame with an unsupported body
* but happens to have an identifier that is valid for another version of the tag it will return true
*
* @param identifier start of frameId to lookup
* @return tag has frame starting with this identifier
*/
public boolean hasFrameOfType(String identifier)
{
Iterator iterator = frameMap.keySet().iterator();
String key;
boolean found = false;
while (iterator.hasNext() && !found)
{
key = (String) iterator.next();
if (key.startsWith(identifier))
{
found = true;
}
}
return found;
}
/**
* For single frames return the frame in this tag with given identifier if it exists, if multiple frames
* exist with the same identifier it will return a list containing all the frames with this identifier
* <p/>
* Warning the match is only done against the identifier so if a tag contains a frame with an unsupported body
* but happens to have an identifier that is valid for another version of the tag it will be returned.
* <p/>
*
* @param identifier is an ID3Frame identifier
* @return matching frame, or list of matching frames
*/
//TODO:This method is problematic because sometimes it returns a list and sometimes a frame, we need to
//replace with two seperate methods as in the tag interface.
public Object getFrame(String identifier)
{
return frameMap.get(identifier);
}
/**
* Retrieve the first value that exists for this identifier
* <p/>
* If the value is a String it returns that, otherwise returns a summary of the fields information
* <p/>
*
* @param identifier
* @return
*/
//TODO:we should be just be using the bodies toString() method so we dont have if statement in this method
//but this is being used by something else at the moment
public String getFirst(String identifier)
{
AbstractID3v2Frame frame = getFirstField(identifier);
if (frame == null)
{
return "";
}
if (frame.getBody() instanceof FrameBodyCOMM)
{
return ((FrameBodyCOMM) frame.getBody()).getText();
}
else if (frame.getBody() instanceof AbstractFrameBodyTextInfo)
{
return ((AbstractFrameBodyTextInfo) frame.getBody()).getFirstTextValue();
}
else if (frame.getBody() instanceof AbstractFrameBodyUrlLink)
{
return ((AbstractFrameBodyUrlLink) frame.getBody()).getUrlLink();
}
else
{
return frame.getBody().toString();
}
}
/**
* Retrieve the first tagfield that exists for this identifier
*
* @param identifier
* @return tag field or null if doesnt exist
*/
public AbstractID3v2Frame getFirstField(String identifier)
{
Object object = getFrame(identifier);
if (object == null)
{
return null;
}
if (object instanceof List)
{
return (AbstractID3v2Frame) ((List) object).get(0);
}
else
{
return (AbstractID3v2Frame) object;
}
}
/**
* Add a frame to this tag
*
* @param frame the frame to add
* <p/>
* <p/>
* Warning if frame(s) already exists for this identifier thay are overwritten
* <p/>
*/
//TODO needs to ensure do not add an invalid frame for this tag
//TODO what happens if already contains a list with this ID
public void setFrame(AbstractID3v2Frame frame)
{
frameMap.put(frame.getIdentifier(), frame);
}
protected abstract ID3Frames getID3Frames();
/**
* @param field
* @throws FieldDataInvalidException
*/
public void set(TagField field) throws FieldDataInvalidException
{
if (!(field instanceof AbstractID3v2Frame))
{
throw new FieldDataInvalidException("Field " + field + " is not of type AbstractID3v2Frame");
}
AbstractID3v2Frame newFrame = (AbstractID3v2Frame) field;
Object o = frameMap.get(field.getId());
if (o == null || (!getID3Frames().isMultipleAllowed(newFrame.getId())))
{
System.out.println("Replacing....");
frameMap.put(field.getId(), field);
}
else if (o instanceof AbstractID3v2Frame)
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -