package org.jaudiotagger.tag.mp4.atom; import org.jaudiotagger.audio.generic.Utils; import org.jaudiotagger.audio.mp4.atom.AbstractMp4Box; import org.jaudiotagger.audio.mp4.atom.Mp4BoxHeader; import org.jaudiotagger.tag.mp4.field.Mp4FieldType; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; /** * This box is used within both normal metadat boxes and ---- boxes to hold the actual data. * <p/> * <p>Format is as follows: * :length (4 bytes) * :name 'Data' (4 bytes) * :atom version (1 byte) * :atom type flags (3 bytes) * :locale field (4 bytes) //Currently always zero * :data */ public class Mp4DataBox extends AbstractMp4Box { public static final String IDENTIFIER = "data"; public static final int VERSION_LENGTH = 1; public static final int TYPE_LENGTH = 3; public static final int NULL_LENGTH = 4; public static final int PRE_DATA_LENGTH = VERSION_LENGTH + TYPE_LENGTH + NULL_LENGTH; public static final int DATA_HEADER_LENGTH = Mp4BoxHeader.HEADER_LENGTH + PRE_DATA_LENGTH; public static final int TYPE_POS = VERSION_LENGTH; //For use externally public static final int TYPE_POS_INCLUDING_HEADER = Mp4BoxHeader.HEADER_LENGTH + TYPE_POS; private int type; private String content; public static final int NUMBER_LENGTH = 2; //Holds the numbers decoded private List<Short> numbers; //Holds bytedata for byte fields as is not clear for multibyte fields exactly these should be wrttien private byte[] bytedata; /** * @param header parentHeader info * @param dataBuffer data of box (doesnt include parentHeader data) */ public Mp4DataBox(Mp4BoxHeader header, ByteBuffer dataBuffer) { this.header = header; //Double check if (!header.getId().equals(IDENTIFIER)) { throw new RuntimeException("Unable to process data box because identifier is:" + header.getId()); } //Make slice so operations here don't effect position of main buffer this.dataBuffer = dataBuffer.slice(); //Type type = Utils.getIntBE(this.dataBuffer, Mp4DataBox.TYPE_POS, Mp4DataBox.TYPE_POS + Mp4DataBox.TYPE_LENGTH - 1); if (type == Mp4FieldType.TEXT.getFileClassId()) { content = Utils.getString(this.dataBuffer, PRE_DATA_LENGTH, header.getDataLength() - PRE_DATA_LENGTH, header.getEncoding()); } else if (type == Mp4FieldType.IMPLICIT.getFileClassId()) { numbers = new ArrayList<Short>(); for (int i = 0; i < ((header.getDataLength() - PRE_DATA_LENGTH) / NUMBER_LENGTH); i++) { short number = Utils.getShortBE(this.dataBuffer, PRE_DATA_LENGTH + (i * NUMBER_LENGTH), PRE_DATA_LENGTH + (i * NUMBER_LENGTH) + (NUMBER_LENGTH - 1)); numbers.add(number); } //Make String representation (separate values with slash) StringBuffer sb = new StringBuffer(); ListIterator<Short> iterator = numbers.listIterator(); while (iterator.hasNext()) { sb.append(iterator.next()); if (iterator.hasNext()) { sb.append("/"); } } content = sb.toString(); } else if (type == Mp4FieldType.INTEGER.getFileClassId()) { //TODO byte data length seems to be 1 for pgap and cpil but 2 for tmpo ? //Create String representation for display content = Utils.getIntBE(this.dataBuffer, PRE_DATA_LENGTH, header.getDataLength() - 1) + ""; //But store data for safer writing back to file bytedata = new byte[header.getDataLength() - PRE_DATA_LENGTH]; int pos = dataBuffer.position(); dataBuffer.position(pos + PRE_DATA_LENGTH); dataBuffer.get(bytedata); dataBuffer.position(pos); //Songbird uses this type for trkn atom (normally implicit type) is used so just added this code so can be used //by the Mp4TrackField atom numbers = new ArrayList<Short>(); for (int i = 0; i < ((header.getDataLength() - PRE_DATA_LENGTH) / NUMBER_LENGTH); i++) { short number = Utils.getShortBE(this.dataBuffer, PRE_DATA_LENGTH + (i * NUMBER_LENGTH), PRE_DATA_LENGTH + (i * NUMBER_LENGTH) + (NUMBER_LENGTH - 1)); numbers.add(number); } } else if (type == Mp4FieldType.COVERART_JPEG.getFileClassId()) { content = Utils.getString(this.dataBuffer, PRE_DATA_LENGTH, header.getDataLength() - PRE_DATA_LENGTH, header.getEncoding()); } } public String getContent() { return content; } public int getType() { return type; } /** * Return numbers, only valid for numeric fields * * @return numbers */ //TODO this is only applicable for numeric databoxes, should we subclass dont know type until start //constructing and we also have Mp4tagTextNumericField class as well public List<Short> getNumbers() { return numbers; } /** * Return raw byte data only vaid for byte fields * * @return byte data */ public byte[] getByteData() { return bytedata; } }