From 3a76bbcc05ec816a6fd03c924566eb5b4c6d354d Mon Sep 17 00:00:00 2001 From: Code0987 Date: Thu, 23 Mar 2017 00:30:39 +0530 Subject: [PATCH 1/9] added TXXX based tags reader support --- .../mpatric/mp3agic/ID3v2TXXXFrameData.java | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java diff --git a/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java b/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java new file mode 100644 index 00000000..9d44a80b --- /dev/null +++ b/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java @@ -0,0 +1,139 @@ +package com.mpatric.mp3agic; + +import android.text.TextUtils; + +import java.util.ArrayList; + +public class ID3v2TXXXFrameData extends AbstractID3v2FrameData { + + protected static final String TAG = "ID3::TXXX"; + + public static final String ID_FIELD = "TXXX"; + + private EncodedText description; + private EncodedText value; + + public ID3v2TXXXFrameData(boolean unsynchronisation) { + super(unsynchronisation); + } + + public ID3v2TXXXFrameData(boolean unsynchronisation, EncodedText description, EncodedText value) { + super(unsynchronisation); + this.description = description; + this.value = value; + } + + public ID3v2TXXXFrameData(boolean unsynchronisation, byte[] bytes) throws InvalidDataException { + super(unsynchronisation); + synchroniseAndUnpackFrameData(bytes); + } + + @Override + protected void unpackFrameData(byte[] bytes) throws InvalidDataException { + int marker = BufferTools.indexOfTerminatorForEncoding(bytes, 1, bytes[0]); + + description = new EncodedText(bytes[0], BufferTools.copyBuffer(bytes, 1, marker - 1)); + + marker += description.getTerminator().length; + + value = new EncodedText(bytes[0], BufferTools.copyBuffer(bytes, marker, bytes.length - marker)); + } + + @Override + protected byte[] packFrameData() { + byte[] bytes = new byte[getLength()]; + if (value != null) bytes[0] = value.getTextEncoding(); + else bytes[0] = 0; + int marker = 1; + if (description != null) { + byte[] descriptionBytes = description.toBytes(true, true); + BufferTools.copyIntoByteBuffer(descriptionBytes, 0, descriptionBytes.length, bytes, marker); + marker += descriptionBytes.length; + } else { + bytes[marker++] = 0; + } + if (value != null) { + byte[] commentBytes = value.toBytes(true, false); + BufferTools.copyIntoByteBuffer(commentBytes, 0, commentBytes.length, bytes, marker); + } + return bytes; + } + + @Override + protected int getLength() { + int length = 1; + if (description != null) length += description.toBytes(true, true).length; + else length++; + if (value != null) length += value.toBytes(true, false).length; + return length; + } + + public EncodedText getValue() { + return value; + } + + public void setValue(EncodedText value) { + this.value = value; + } + + public EncodedText getDescription() { + return description; + } + + public void setDescription(EncodedText description) { + this.description = description; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + result = prime * result + ((description == null) ? 0 : description.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + return false; + } + + public static ArrayList extractAll(ID3v2FrameSet txxx, boolean useFrameUnsynchronisation, String d) { + if (txxx != null) { + + ArrayList fields = new ArrayList<>(); + + for (ID3v2Frame frame : txxx.getFrames()) + try { + ID3v2TXXXFrameData field = new ID3v2TXXXFrameData(useFrameUnsynchronisation, frame.getData()); + + if (d == null || TextUtils.isEmpty(d) || field.getDescription().toString().contains(d)) + fields.add(field); + } catch (InvalidDataException e) { + // do nothing + } + + return fields; + } + + return null; + } + + public static ArrayList extractAll(ID3v2FrameSet txxx, boolean useFrameUnsynchronisation) { + return extractAll(txxx, useFrameUnsynchronisation, null); + } + + public static ID3v2TXXXFrameData extract(ID3v2FrameSet txxx, boolean useFrameUnsynchronisation, String d) { + ArrayList items = extractAll(txxx, useFrameUnsynchronisation, d); + if (items.size() > 0) + return items.get(0); + + return null; + } +} From 43a4827d322eaa59a7ddb06eef6d2ec86cccad08 Mon Sep 17 00:00:00 2001 From: Code0987 Date: Thu, 6 Apr 2017 02:06:53 +0530 Subject: [PATCH 2/9] fixed import issue --- src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java b/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java index 9d44a80b..ff1595f8 100644 --- a/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java +++ b/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java @@ -1,7 +1,5 @@ package com.mpatric.mp3agic; -import android.text.TextUtils; - import java.util.ArrayList; public class ID3v2TXXXFrameData extends AbstractID3v2FrameData { @@ -113,7 +111,7 @@ public static ArrayList extractAll(ID3v2FrameSet txxx, boole try { ID3v2TXXXFrameData field = new ID3v2TXXXFrameData(useFrameUnsynchronisation, frame.getData()); - if (d == null || TextUtils.isEmpty(d) || field.getDescription().toString().contains(d)) + if (d == null || field.getDescription().toString().contains(d)) fields.add(field); } catch (InvalidDataException e) { // do nothing From 7c46d3beb119122b0abd94f108c5b66ffd4891fb Mon Sep 17 00:00:00 2001 From: Code0987 Date: Sun, 9 Apr 2017 15:41:14 +0530 Subject: [PATCH 3/9] updates, added tests --- .gitignore | 21 +- pom.xml | 6 + .../com/mpatric/mp3agic/AbstractID3v2Tag.java | 2443 +++++++++-------- src/main/java/com/mpatric/mp3agic/ID3v2.java | 5 +- .../mpatric/mp3agic/ID3v2TXXXFrameData.java | 143 +- .../com/mpatric/mp3agic/ID3WrapperTest.java | 9 + .../mp3agic/ID3v2TXXXFrameDataTest.java | 113 + 7 files changed, 1544 insertions(+), 1196 deletions(-) create mode 100644 src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java diff --git a/.gitignore b/.gitignore index e71582c6..3e5c8484 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ -.DS_Store -bin -test-bin -build -*.iml -.idea -*.project -*.classpath -target -.settings +.DS_Store +bin +test-bin +build +*.iml +.idea +*.project +*.classpath +target +.settings +/nbproject/ \ No newline at end of file diff --git a/pom.xml b/pom.xml index b13f0f28..3f0549bf 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,12 @@ 4.12 test + + org.hamcrest + hamcrest-core + 1.3 + test + diff --git a/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java b/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java index 3d8c6e43..192413f1 100644 --- a/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java +++ b/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java @@ -5,970 +5,1035 @@ public abstract class AbstractID3v2Tag implements ID3v2 { - public static final String ID_IMAGE = "APIC"; - public static final String ID_ENCODER = "TENC"; - public static final String ID_URL = "WXXX"; - public static final String ID_ARTIST_URL = "WOAR"; - public static final String ID_COMMERCIAL_URL = "WCOM"; - public static final String ID_COPYRIGHT_URL = "WCOP"; - public static final String ID_AUDIOFILE_URL = "WOAF"; - public static final String ID_AUDIOSOURCE_URL = "WOAS"; - public static final String ID_RADIOSTATION_URL = "WORS"; - public static final String ID_PAYMENT_URL = "WPAY"; - public static final String ID_PUBLISHER_URL = "WPUB"; - public static final String ID_COPYRIGHT = "TCOP"; - public static final String ID_ORIGINAL_ARTIST = "TOPE"; - public static final String ID_BPM = "TBPM"; - public static final String ID_COMPOSER = "TCOM"; - public static final String ID_PUBLISHER = "TPUB"; - public static final String ID_COMMENT = "COMM"; - public static final String ID_TEXT_LYRICS = "USLT"; - public static final String ID_GENRE = "TCON"; - public static final String ID_YEAR = "TYER"; - public static final String ID_DATE = "TDAT"; - public static final String ID_ALBUM = "TALB"; - public static final String ID_TITLE = "TIT2"; - public static final String ID_KEY = "TKEY"; - public static final String ID_ARTIST = "TPE1"; - public static final String ID_ALBUM_ARTIST = "TPE2"; - public static final String ID_TRACK = "TRCK"; - public static final String ID_PART_OF_SET = "TPOS"; - public static final String ID_COMPILATION = "TCMP"; - public static final String ID_CHAPTER_TOC = "CTOC"; - public static final String ID_CHAPTER = "CHAP"; - public static final String ID_GROUPING = "TIT1"; - public static final String ID_RATING = "POPM"; - public static final String ID_IMAGE_OBSELETE = "PIC"; - public static final String ID_ENCODER_OBSELETE = "TEN"; - public static final String ID_URL_OBSELETE = "WXX"; - public static final String ID_COPYRIGHT_OBSELETE = "TCR"; - public static final String ID_ORIGINAL_ARTIST_OBSELETE = "TOA"; - public static final String ID_BPM_OBSELETE = "TBP"; - public static final String ID_COMPOSER_OBSELETE = "TCM"; - public static final String ID_PUBLISHER_OBSELETE = "TBP"; - public static final String ID_COMMENT_OBSELETE = "COM"; - public static final String ID_GENRE_OBSELETE = "TCO"; - public static final String ID_YEAR_OBSELETE = "TYE"; - public static final String ID_DATE_OBSELETE = "TDA"; - public static final String ID_ALBUM_OBSELETE = "TAL"; - public static final String ID_TITLE_OBSELETE = "TT2"; - public static final String ID_KEY_OBSELETE = "TKE"; - public static final String ID_ARTIST_OBSELETE = "TP1"; - public static final String ID_ALBUM_ARTIST_OBSELETE = "TP2"; - public static final String ID_TRACK_OBSELETE = "TRK"; - public static final String ID_PART_OF_SET_OBSELETE = "TPA"; - public static final String ID_COMPILATION_OBSELETE = "TCP"; - public static final String ID_GROUPING_OBSELETE = "TT1"; - - public static final byte PICTURETYPE_OTHER = 0x0; - public static final byte PICTURETYPE_32PXICON = 0x1; - public static final byte PICTURETYPE_OTHERICON = 0x2; - public static final byte PICTURETYPE_FRONTCOVER = 0x3; - public static final byte PICTURETYPE_BACKCOVER = 0x4; - public static final byte PICTURETYPE_LEAFLET = 0x5; - public static final byte PICTURETYPE_MEDIA = 0x6; - public static final byte PICTURETYPE_LEADARTIST = 0x7; - public static final byte PICTURETYPE_ARTIST = 0x8; - public static final byte PICTURETYPE_CONDUCTOR = 0x9; - public static final byte PICTURETYPE_BAND = 0xA; - public static final byte PICTURETYPE_COMPOSER = 0xB; - public static final byte PICTURETYPE_LYRICIST = 0xC; - public static final byte PICTURETYPE_RECORDINGLOCATION = 0xD; - public static final byte PICTURETYPE_DURING_RECORDING = 0xE; - public static final byte PICTURETYPE_DURING_PERFORMANCE = 0xF; - public static final byte PICTURETYPE_SCREEN_CAPTURE = 0x10; - public static final byte PICTURETYPE_ILLUSTRATION = 0x12; - public static final byte PICTURETYPE_BAND_LOGOTYPE = 0x13; - public static final byte PICTURETYPE_PUBLISHER_LOGOTYPE = 0x14; - - - protected static final String TAG = "ID3"; - protected static final String FOOTER_TAG = "3DI"; - protected static final int HEADER_LENGTH = 10; - protected static final int FOOTER_LENGTH = 10; - protected static final int MAJOR_VERSION_OFFSET = 3; - protected static final int MINOR_VERSION_OFFSET = 4; - protected static final int FLAGS_OFFSET = 5; - protected static final int DATA_LENGTH_OFFSET = 6; - protected static final int FOOTER_BIT = 4; - protected static final int EXPERIMENTAL_BIT = 5; - protected static final int EXTENDED_HEADER_BIT = 6; - protected static final int COMPRESSION_BIT = 6; - protected static final int UNSYNCHRONISATION_BIT = 7; - protected static final int PADDING_LENGTH = 256; - private static final String ITUNES_COMMENT_DESCRIPTION = "iTunNORM"; - - protected boolean unsynchronisation = false; - protected boolean extendedHeader = false; - protected boolean experimental = false; - protected boolean footer = false; - protected boolean compression = false; - protected boolean padding = false; - protected String version = null; - private int dataLength = 0; - private int extendedHeaderLength; - private byte[] extendedHeaderData; - private boolean obseleteFormat = false; - - private final Map frameSets; - - public AbstractID3v2Tag() { - frameSets = new TreeMap<>(); - } - - public AbstractID3v2Tag(byte[] bytes) throws NoSuchTagException, UnsupportedTagException, InvalidDataException { - this(bytes, false); - } - - public AbstractID3v2Tag(byte[] bytes, boolean obseleteFormat) throws NoSuchTagException, UnsupportedTagException, InvalidDataException { - frameSets = new TreeMap<>(); - this.obseleteFormat = obseleteFormat; - unpackTag(bytes); - } - - private void unpackTag(byte[] bytes) throws NoSuchTagException, UnsupportedTagException, InvalidDataException { - ID3v2TagFactory.sanityCheckTag(bytes); - int offset = unpackHeader(bytes); - try { - if (extendedHeader) { - offset = unpackExtendedHeader(bytes, offset); - } - int framesLength = dataLength; - if (footer) framesLength -= 10; - offset = unpackFrames(bytes, offset, framesLength); - if (footer) { - offset = unpackFooter(bytes, dataLength); - } - } catch (ArrayIndexOutOfBoundsException e) { - throw new InvalidDataException("Premature end of tag", e); - } - } - - private int unpackHeader(byte[] bytes) throws UnsupportedTagException, InvalidDataException { - int majorVersion = bytes[MAJOR_VERSION_OFFSET]; - int minorVersion = bytes[MINOR_VERSION_OFFSET]; - version = majorVersion + "." + minorVersion; - if (majorVersion != 2 && majorVersion != 3 && majorVersion != 4) { - throw new UnsupportedTagException("Unsupported version " + version); - } - unpackFlags(bytes); - if ((bytes[FLAGS_OFFSET] & 0x0F) != 0) throw new UnsupportedTagException("Unrecognised bits in header"); - dataLength = BufferTools.unpackSynchsafeInteger(bytes[DATA_LENGTH_OFFSET], bytes[DATA_LENGTH_OFFSET + 1], bytes[DATA_LENGTH_OFFSET + 2], bytes[DATA_LENGTH_OFFSET + 3]); - if (dataLength < 1) throw new InvalidDataException("Zero size tag"); - return HEADER_LENGTH; - } - - protected abstract void unpackFlags(byte[] bytes); - - private int unpackExtendedHeader(byte[] bytes, int offset) { - extendedHeaderLength = BufferTools.unpackSynchsafeInteger(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]) + 4; - extendedHeaderData = BufferTools.copyBuffer(bytes, offset + 4, extendedHeaderLength); - return extendedHeaderLength; - } - - protected int unpackFrames(byte[] bytes, int offset, int framesLength) { - int currentOffset = offset; - while (currentOffset <= framesLength) { - ID3v2Frame frame; - try { - frame = createFrame(bytes, currentOffset); - addFrame(frame, false); - currentOffset += frame.getLength(); - } catch (InvalidDataException e) { - break; - } - } - return currentOffset; - } - - protected void addFrame(ID3v2Frame frame, boolean replace) { - ID3v2FrameSet frameSet = frameSets.get(frame.getId()); - if (frameSet == null) { - frameSet = new ID3v2FrameSet(frame.getId()); - frameSet.addFrame(frame); - frameSets.put(frame.getId(), frameSet); - } else if (replace) { - frameSet.clear(); - frameSet.addFrame(frame); - } else { - frameSet.addFrame(frame); - } - } - - protected ID3v2Frame createFrame(byte[] bytes, int currentOffset) throws InvalidDataException { - if (obseleteFormat) return new ID3v2ObseleteFrame(bytes, currentOffset); - return new ID3v2Frame(bytes, currentOffset); - } - - protected ID3v2Frame createFrame(String id, byte[] data) { - if (obseleteFormat) return new ID3v2ObseleteFrame(id, data); - else return new ID3v2Frame(id, data); - } - - private int unpackFooter(byte[] bytes, int offset) throws InvalidDataException { - if (! FOOTER_TAG.equals(BufferTools.byteBufferToStringIgnoringEncodingIssues(bytes, offset, FOOTER_TAG.length()))) { - throw new InvalidDataException("Invalid footer"); - } - return FOOTER_LENGTH; - } - - @Override - public byte[] toBytes() throws NotSupportedException { - byte[] bytes = new byte[getLength()]; - packTag(bytes); - return bytes; - } - - public void packTag(byte[] bytes) throws NotSupportedException { - int offset = packHeader(bytes, 0); - if (extendedHeader) { - offset = packExtendedHeader(bytes, offset); - } - offset = packFrames(bytes, offset); - if (footer) { - offset = packFooter(bytes, dataLength); - } - } - - private int packHeader(byte[] bytes, int offset) { - try { - BufferTools.stringIntoByteBuffer(TAG, 0, TAG.length(), bytes, offset); - } catch (UnsupportedEncodingException e) { - } - String s[] = version.split("\\."); - if (s.length > 0) { - byte majorVersion = Byte.parseByte(s[0]); - bytes[offset + MAJOR_VERSION_OFFSET] = majorVersion; - } - if (s.length > 1) { - byte minorVersion = Byte.parseByte(s[1]); - bytes[offset + MINOR_VERSION_OFFSET] = minorVersion; - } - packFlags(bytes, offset); - BufferTools.packSynchsafeInteger(getDataLength(), bytes, offset + DATA_LENGTH_OFFSET); - return offset + HEADER_LENGTH; - } - - protected abstract void packFlags(byte[] bytes, int i); - - private int packExtendedHeader(byte[] bytes, int offset) { - BufferTools.packSynchsafeInteger(extendedHeaderLength, bytes, offset); - BufferTools.copyIntoByteBuffer(extendedHeaderData, 0, extendedHeaderData.length, bytes, offset + 4); - return offset + 4 + extendedHeaderData.length; - } - - public int packFrames(byte[] bytes, int offset) throws NotSupportedException { - int newOffset = packSpecifiedFrames(bytes, offset, null, "APIC"); - newOffset = packSpecifiedFrames(bytes, newOffset, "APIC", null); - return newOffset; - } - - private int packSpecifiedFrames(byte[] bytes, int offset, String onlyId, String notId) throws NotSupportedException { - Iterator setIterator = frameSets.values().iterator(); - while (setIterator.hasNext()) { - ID3v2FrameSet frameSet = setIterator.next(); - if ((onlyId == null || onlyId.equals(frameSet.getId())) && (notId == null || !notId.equals(frameSet.getId()))) { - Iterator frameIterator = frameSet.getFrames().iterator(); - while (frameIterator.hasNext()) { - ID3v2Frame frame = (ID3v2Frame) frameIterator.next(); - if (frame.getDataLength() > 0) { - byte[] frameData = frame.toBytes(); - BufferTools.copyIntoByteBuffer(frameData, 0, frameData.length, bytes, offset); - offset += frameData.length; - } - } - } - } - return offset; - } - - private int packFooter(byte[] bytes, int offset) { - try { - BufferTools.stringIntoByteBuffer(FOOTER_TAG, 0, FOOTER_TAG.length(), bytes, offset); - } catch (UnsupportedEncodingException e) { - } - String s[] = version.split("\\."); - if (s.length > 0) { - byte majorVersion = Byte.parseByte(s[0]); - bytes[offset + MAJOR_VERSION_OFFSET] = majorVersion; - } - if (s.length > 1) { - byte minorVersion = Byte.parseByte(s[1]); - bytes[offset + MINOR_VERSION_OFFSET] = minorVersion; - } - packFlags(bytes, offset); - BufferTools.packSynchsafeInteger(getDataLength(), bytes, offset + DATA_LENGTH_OFFSET); - return offset + FOOTER_LENGTH; - } - - private int calculateDataLength() { - int length = 0; - if (extendedHeader) length += extendedHeaderLength; - if (footer) length += FOOTER_LENGTH; - else if (padding) length += PADDING_LENGTH; - Iterator setIterator = frameSets.values().iterator(); - while (setIterator.hasNext()) { - ID3v2FrameSet frameSet = setIterator.next(); - Iterator frameIterator = frameSet.getFrames().iterator(); - while (frameIterator.hasNext()) { - ID3v2Frame frame = (ID3v2Frame) frameIterator.next(); - length += frame.getLength(); - } - } - return length; - } - - protected boolean useFrameUnsynchronisation() { - return false; - } - - @Override - public String getVersion() { - return version; - } - - protected void invalidateDataLength() { - dataLength = 0; - } - - @Override - public int getDataLength() { - if (dataLength == 0) { - dataLength = calculateDataLength(); - } - return dataLength; - } - - @Override - public int getLength() { - return getDataLength() + HEADER_LENGTH; - } - - @Override - public Map getFrameSets() { - return frameSets; - } - - @Override - public boolean getPadding() { - return padding; - } - - @Override - public void setPadding(boolean padding) { - if (this.padding != padding) { - invalidateDataLength(); - this.padding = padding; - } - } - - @Override - public boolean hasFooter() { - return footer; - } - - @Override - public void setFooter(boolean footer) { - if (this.footer != footer) { - invalidateDataLength(); - this.footer = footer; - } - } - - @Override - public boolean hasUnsynchronisation() { - return unsynchronisation; - } - - @Override - public void setUnsynchronisation(boolean unsynchronisation) { - if (this.unsynchronisation != unsynchronisation) { - invalidateDataLength(); - this.unsynchronisation = unsynchronisation; - } - } - - @Override - public boolean getObseleteFormat() { - return obseleteFormat; - } - - @Override - public String getTrack() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_TRACK_OBSELETE : ID_TRACK); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setTrack(String track) { - if (track != null && track.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(track)); - addFrame(createFrame(ID_TRACK, frameData.toBytes()), true); - } - } - - @Override - public String getPartOfSet() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_PART_OF_SET_OBSELETE : ID_PART_OF_SET); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setPartOfSet(String partOfSet) { - if (partOfSet != null && partOfSet.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(partOfSet)); - addFrame(createFrame(ID_PART_OF_SET, frameData.toBytes()), true); - } - } - - @Override - public boolean isCompilation() { - // unofficial frame used by iTunes - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_COMPILATION_OBSELETE : ID_COMPILATION); - if (frameData != null && frameData.getText() != null) return "1".equals(frameData.getText().toString()); - return false; - } - - @Override - public void setCompilation(boolean compilation) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(compilation ? "1" : "0")); - addFrame(createFrame(ID_COMPILATION, frameData.toBytes()), true); - } - - @Override - public String getGrouping() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_GROUPING_OBSELETE : ID_GROUPING); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setGrouping(String grouping) { - if (grouping != null && grouping.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(grouping)); - addFrame(createFrame(ID_GROUPING, frameData.toBytes()), true); - } - } - - @Override - public String getArtist() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_ARTIST_OBSELETE : ID_ARTIST); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setArtist(String artist) { - if (artist != null && artist.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(artist)); - addFrame(createFrame(ID_ARTIST, frameData.toBytes()), true); - } - } - - @Override - public String getAlbumArtist() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_ALBUM_ARTIST_OBSELETE : ID_ALBUM_ARTIST); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setAlbumArtist(String albumArtist) { - if (albumArtist != null && albumArtist.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(albumArtist)); - addFrame(createFrame(ID_ALBUM_ARTIST, frameData.toBytes()), true); - } - } - - @Override - public String getTitle() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_TITLE_OBSELETE : ID_TITLE); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setTitle(String title) { - if (title != null && title.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(title)); - addFrame(createFrame(ID_TITLE, frameData.toBytes()), true); - } - } - - @Override - public String getAlbum() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_ALBUM_OBSELETE : ID_ALBUM); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setAlbum(String album) { - if (album != null && album.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(album)); - addFrame(createFrame(ID_ALBUM, frameData.toBytes()), true); - } - } - - @Override - public String getYear() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_YEAR_OBSELETE : ID_YEAR); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setYear(String year) { - if (year != null && year.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(year)); - addFrame(createFrame(ID_YEAR, frameData.toBytes()), true); - } - } - - @Override - public String getDate() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_DATE_OBSELETE : ID_DATE); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setDate(String date) { - if (date != null && date.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(date)); - addFrame(createFrame(ID_DATE, frameData.toBytes()), true); - } - } - - private int getGenre(String text) { - if (text != null && text.length() > 0) { - try { - return extractGenreNumber(text); - } catch (NumberFormatException e) { // match genre description - String description = extractGenreDescription(text); - return ID3v1Genres.matchGenreDescription(description); - } - } - return -1; - } - - @Override - public int getGenre() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_GENRE_OBSELETE : ID_GENRE); - if (frameData == null || frameData.getText() == null) { - return -1; - } - return getGenre(frameData.getText().toString()); - } - - @Override - public void setGenre(int genre) { - if (genre >= 0) { - invalidateDataLength(); - String genreDescription = genre < ID3v1Genres.GENRES.length ? ID3v1Genres.GENRES[genre] : ""; - String combinedGenre = "(" + Integer.toString(genre) + ")" + genreDescription; - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(combinedGenre)); - addFrame(createFrame(ID_GENRE, frameData.toBytes()), true); - } else { - // TODO remove frame? - } - } - - @Override - public int getBPM() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_BPM_OBSELETE : ID_BPM); - if (frameData == null || frameData.getText() == null) { - return -1; - } - String bpmStr = frameData.getText().toString(); - try { - return Integer.parseInt(bpmStr); - } catch (NumberFormatException e) { - // try float as some utilities add BPM like 67.8, or 67,8 - return (int)Float.parseFloat(bpmStr.trim().replaceAll(",",".")); - } - } - - @Override - public void setBPM(int bpm) { - if (bpm >= 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(Integer.toString(bpm))); - addFrame(createFrame(ID_BPM, frameData.toBytes()), true); - } - } - - @Override - public String getKey() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_KEY_OBSELETE : ID_KEY); - if (frameData == null || frameData.getText() == null) { - return null; - } - return frameData.getText().toString(); - } - - @Override - public void setKey(String key) { - if (key != null && key.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(key)); - addFrame(createFrame(ID_KEY, frameData.toBytes()), true); - } - } - - @Override - public String getGenreDescription() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_GENRE_OBSELETE : ID_GENRE); - if (frameData == null || frameData.getText() == null) { - return null; - } - String text = frameData.getText().toString(); - if (text != null) { - int genreNum = getGenre(text); - if (genreNum >= 0 && genreNum < ID3v1Genres.GENRES.length) { - return ID3v1Genres.GENRES[genreNum]; - } else { - String description = extractGenreDescription(text); - if (description != null && description.length() > 0) { - return description; - } - } - } - return null; - } - - @Override - public void setGenreDescription(String text) throws IllegalArgumentException { - int genreNum = ID3v1Genres.matchGenreDescription(text); - if (genreNum < 0) { - throw new IllegalArgumentException("Unknown genre: " + text); - } - setGenre(genreNum); - } - - protected int extractGenreNumber(String genreValue) throws NumberFormatException { - String value = genreValue.trim(); - if (value.length() > 0) { - if (value.charAt(0) == '(') { - int pos = value.indexOf(')'); - if (pos > 0) { - return Integer.parseInt(value.substring(1, pos)); - } - } - } - return Integer.parseInt(value); - } - - protected String extractGenreDescription(String genreValue) throws NumberFormatException { - String value = genreValue.trim(); - if (value.length() > 0) { - if (value.charAt(0) == '(') { - int pos = value.indexOf(')'); - if (pos > 0) { - return value.substring(pos + 1); - } - } - return value; - } - return null; - } - - - @Override - public String getComment() { - ID3v2CommentFrameData frameData = extractCommentFrameData(obseleteFormat ? ID_COMMENT_OBSELETE : ID_COMMENT, false); - if (frameData != null && frameData.getComment() != null) return frameData.getComment().toString(); - return null; - } - - @Override - public void setComment(String comment) { - if (comment != null && comment.length() > 0) { - invalidateDataLength(); - ID3v2CommentFrameData frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), "eng", null, new EncodedText(comment)); - addFrame(createFrame(ID_COMMENT, frameData.toBytes()), true); - } - } - - @Override - public String getItunesComment() { - ID3v2CommentFrameData frameData = extractCommentFrameData(obseleteFormat ? ID_COMMENT_OBSELETE : ID_COMMENT, true); - if (frameData != null && frameData.getComment() != null) return frameData.getComment().toString(); - return null; - } - - @Override - public void setItunesComment(String itunesComment) { - if (itunesComment != null && itunesComment.length() > 0) { - invalidateDataLength(); - ID3v2CommentFrameData frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), "eng", new EncodedText(ITUNES_COMMENT_DESCRIPTION), new EncodedText(itunesComment)); - addFrame(createFrame(ID_COMMENT, frameData.toBytes()), true); - } - } - - protected ID3v2CommentFrameData extractLyricsFrameData(String id) - { - ID3v2FrameSet frameSet = frameSets.get(id); - if (frameSet != null) { - Iterator iterator = frameSet.getFrames().iterator(); - while (iterator.hasNext()) { - ID3v2Frame frame = (ID3v2Frame) iterator.next(); - ID3v2CommentFrameData frameData; - try { - frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), frame.getData()); - return frameData; - } catch (InvalidDataException e) { - // Do nothing + public static final String ID_IMAGE = "APIC"; + public static final String ID_ENCODER = "TENC"; + public static final String ID_URL = "WXXX"; + public static final String ID_ARTIST_URL = "WOAR"; + public static final String ID_COMMERCIAL_URL = "WCOM"; + public static final String ID_COPYRIGHT_URL = "WCOP"; + public static final String ID_AUDIOFILE_URL = "WOAF"; + public static final String ID_AUDIOSOURCE_URL = "WOAS"; + public static final String ID_RADIOSTATION_URL = "WORS"; + public static final String ID_PAYMENT_URL = "WPAY"; + public static final String ID_PUBLISHER_URL = "WPUB"; + public static final String ID_COPYRIGHT = "TCOP"; + public static final String ID_ORIGINAL_ARTIST = "TOPE"; + public static final String ID_BPM = "TBPM"; + public static final String ID_COMPOSER = "TCOM"; + public static final String ID_PUBLISHER = "TPUB"; + public static final String ID_COMMENT = "COMM"; + public static final String ID_TEXT_LYRICS = "USLT"; + public static final String ID_GENRE = "TCON"; + public static final String ID_YEAR = "TYER"; + public static final String ID_DATE = "TDAT"; + public static final String ID_ALBUM = "TALB"; + public static final String ID_TITLE = "TIT2"; + public static final String ID_KEY = "TKEY"; + public static final String ID_ARTIST = "TPE1"; + public static final String ID_ALBUM_ARTIST = "TPE2"; + public static final String ID_TRACK = "TRCK"; + public static final String ID_PART_OF_SET = "TPOS"; + public static final String ID_COMPILATION = "TCMP"; + public static final String ID_CHAPTER_TOC = "CTOC"; + public static final String ID_CHAPTER = "CHAP"; + public static final String ID_GROUPING = "TIT1"; + public static final String ID_RATING = "POPM"; + public static final String ID_IMAGE_OBSELETE = "PIC"; + public static final String ID_ENCODER_OBSELETE = "TEN"; + public static final String ID_URL_OBSELETE = "WXX"; + public static final String ID_COPYRIGHT_OBSELETE = "TCR"; + public static final String ID_ORIGINAL_ARTIST_OBSELETE = "TOA"; + public static final String ID_BPM_OBSELETE = "TBP"; + public static final String ID_COMPOSER_OBSELETE = "TCM"; + public static final String ID_PUBLISHER_OBSELETE = "TBP"; + public static final String ID_COMMENT_OBSELETE = "COM"; + public static final String ID_GENRE_OBSELETE = "TCO"; + public static final String ID_YEAR_OBSELETE = "TYE"; + public static final String ID_DATE_OBSELETE = "TDA"; + public static final String ID_ALBUM_OBSELETE = "TAL"; + public static final String ID_TITLE_OBSELETE = "TT2"; + public static final String ID_KEY_OBSELETE = "TKE"; + public static final String ID_ARTIST_OBSELETE = "TP1"; + public static final String ID_ALBUM_ARTIST_OBSELETE = "TP2"; + public static final String ID_TRACK_OBSELETE = "TRK"; + public static final String ID_PART_OF_SET_OBSELETE = "TPA"; + public static final String ID_COMPILATION_OBSELETE = "TCP"; + public static final String ID_GROUPING_OBSELETE = "TT1"; + + public static final byte PICTURETYPE_OTHER = 0x0; + public static final byte PICTURETYPE_32PXICON = 0x1; + public static final byte PICTURETYPE_OTHERICON = 0x2; + public static final byte PICTURETYPE_FRONTCOVER = 0x3; + public static final byte PICTURETYPE_BACKCOVER = 0x4; + public static final byte PICTURETYPE_LEAFLET = 0x5; + public static final byte PICTURETYPE_MEDIA = 0x6; + public static final byte PICTURETYPE_LEADARTIST = 0x7; + public static final byte PICTURETYPE_ARTIST = 0x8; + public static final byte PICTURETYPE_CONDUCTOR = 0x9; + public static final byte PICTURETYPE_BAND = 0xA; + public static final byte PICTURETYPE_COMPOSER = 0xB; + public static final byte PICTURETYPE_LYRICIST = 0xC; + public static final byte PICTURETYPE_RECORDINGLOCATION = 0xD; + public static final byte PICTURETYPE_DURING_RECORDING = 0xE; + public static final byte PICTURETYPE_DURING_PERFORMANCE = 0xF; + public static final byte PICTURETYPE_SCREEN_CAPTURE = 0x10; + public static final byte PICTURETYPE_ILLUSTRATION = 0x12; + public static final byte PICTURETYPE_BAND_LOGOTYPE = 0x13; + public static final byte PICTURETYPE_PUBLISHER_LOGOTYPE = 0x14; + + protected static final String TAG = "ID3"; + protected static final String FOOTER_TAG = "3DI"; + protected static final int HEADER_LENGTH = 10; + protected static final int FOOTER_LENGTH = 10; + protected static final int MAJOR_VERSION_OFFSET = 3; + protected static final int MINOR_VERSION_OFFSET = 4; + protected static final int FLAGS_OFFSET = 5; + protected static final int DATA_LENGTH_OFFSET = 6; + protected static final int FOOTER_BIT = 4; + protected static final int EXPERIMENTAL_BIT = 5; + protected static final int EXTENDED_HEADER_BIT = 6; + protected static final int COMPRESSION_BIT = 6; + protected static final int UNSYNCHRONISATION_BIT = 7; + protected static final int PADDING_LENGTH = 256; + private static final String ITUNES_COMMENT_DESCRIPTION = "iTunNORM"; + + protected boolean unsynchronisation = false; + protected boolean extendedHeader = false; + protected boolean experimental = false; + protected boolean footer = false; + protected boolean compression = false; + protected boolean padding = false; + protected String version = null; + private int dataLength = 0; + private int extendedHeaderLength; + private byte[] extendedHeaderData; + private boolean obseleteFormat = false; + + private final Map frameSets; + + public AbstractID3v2Tag() { + frameSets = new TreeMap<>(); + } + + public AbstractID3v2Tag(byte[] bytes) throws NoSuchTagException, UnsupportedTagException, InvalidDataException { + this(bytes, false); + } + + public AbstractID3v2Tag(byte[] bytes, boolean obseleteFormat) throws NoSuchTagException, UnsupportedTagException, InvalidDataException { + frameSets = new TreeMap<>(); + this.obseleteFormat = obseleteFormat; + unpackTag(bytes); + } + + private void unpackTag(byte[] bytes) throws NoSuchTagException, UnsupportedTagException, InvalidDataException { + ID3v2TagFactory.sanityCheckTag(bytes); + int offset = unpackHeader(bytes); + try { + if (extendedHeader) { + offset = unpackExtendedHeader(bytes, offset); + } + int framesLength = dataLength; + if (footer) { + framesLength -= 10; + } + offset = unpackFrames(bytes, offset, framesLength); + if (footer) { + offset = unpackFooter(bytes, dataLength); + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new InvalidDataException("Premature end of tag", e); + } + } + + private int unpackHeader(byte[] bytes) throws UnsupportedTagException, InvalidDataException { + int majorVersion = bytes[MAJOR_VERSION_OFFSET]; + int minorVersion = bytes[MINOR_VERSION_OFFSET]; + version = majorVersion + "." + minorVersion; + if (majorVersion != 2 && majorVersion != 3 && majorVersion != 4) { + throw new UnsupportedTagException("Unsupported version " + version); + } + unpackFlags(bytes); + if ((bytes[FLAGS_OFFSET] & 0x0F) != 0) { + throw new UnsupportedTagException("Unrecognised bits in header"); + } + dataLength = BufferTools.unpackSynchsafeInteger(bytes[DATA_LENGTH_OFFSET], bytes[DATA_LENGTH_OFFSET + 1], bytes[DATA_LENGTH_OFFSET + 2], bytes[DATA_LENGTH_OFFSET + 3]); + if (dataLength < 1) { + throw new InvalidDataException("Zero size tag"); + } + return HEADER_LENGTH; + } + + protected abstract void unpackFlags(byte[] bytes); + + private int unpackExtendedHeader(byte[] bytes, int offset) { + extendedHeaderLength = BufferTools.unpackSynchsafeInteger(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]) + 4; + extendedHeaderData = BufferTools.copyBuffer(bytes, offset + 4, extendedHeaderLength); + return extendedHeaderLength; + } + + protected int unpackFrames(byte[] bytes, int offset, int framesLength) { + int currentOffset = offset; + while (currentOffset <= framesLength) { + ID3v2Frame frame; + try { + frame = createFrame(bytes, currentOffset); + addFrame(frame, false); + currentOffset += frame.getLength(); + } catch (InvalidDataException e) { + break; + } + } + return currentOffset; + } + + protected void addFrame(ID3v2Frame frame, boolean replace) { + ID3v2FrameSet frameSet = frameSets.get(frame.getId()); + if (frameSet == null) { + frameSet = new ID3v2FrameSet(frame.getId()); + frameSet.addFrame(frame); + frameSets.put(frame.getId(), frameSet); + } else if (replace) { + frameSet.clear(); + frameSet.addFrame(frame); + } else { + frameSet.addFrame(frame); + } + } + + protected ID3v2Frame createFrame(byte[] bytes, int currentOffset) throws InvalidDataException { + if (obseleteFormat) { + return new ID3v2ObseleteFrame(bytes, currentOffset); + } + return new ID3v2Frame(bytes, currentOffset); + } + + protected ID3v2Frame createFrame(String id, byte[] data) { + if (obseleteFormat) { + return new ID3v2ObseleteFrame(id, data); + } else { + return new ID3v2Frame(id, data); + } + } + + private int unpackFooter(byte[] bytes, int offset) throws InvalidDataException { + if (!FOOTER_TAG.equals(BufferTools.byteBufferToStringIgnoringEncodingIssues(bytes, offset, FOOTER_TAG.length()))) { + throw new InvalidDataException("Invalid footer"); + } + return FOOTER_LENGTH; + } + + @Override + public byte[] toBytes() throws NotSupportedException { + byte[] bytes = new byte[getLength()]; + packTag(bytes); + return bytes; + } + + public void packTag(byte[] bytes) throws NotSupportedException { + int offset = packHeader(bytes, 0); + if (extendedHeader) { + offset = packExtendedHeader(bytes, offset); + } + offset = packFrames(bytes, offset); + if (footer) { + offset = packFooter(bytes, dataLength); + } + } + + private int packHeader(byte[] bytes, int offset) { + try { + BufferTools.stringIntoByteBuffer(TAG, 0, TAG.length(), bytes, offset); + } catch (UnsupportedEncodingException e) { + } + String s[] = version.split("\\."); + if (s.length > 0) { + byte majorVersion = Byte.parseByte(s[0]); + bytes[offset + MAJOR_VERSION_OFFSET] = majorVersion; + } + if (s.length > 1) { + byte minorVersion = Byte.parseByte(s[1]); + bytes[offset + MINOR_VERSION_OFFSET] = minorVersion; + } + packFlags(bytes, offset); + BufferTools.packSynchsafeInteger(getDataLength(), bytes, offset + DATA_LENGTH_OFFSET); + return offset + HEADER_LENGTH; + } + + protected abstract void packFlags(byte[] bytes, int i); + + private int packExtendedHeader(byte[] bytes, int offset) { + BufferTools.packSynchsafeInteger(extendedHeaderLength, bytes, offset); + BufferTools.copyIntoByteBuffer(extendedHeaderData, 0, extendedHeaderData.length, bytes, offset + 4); + return offset + 4 + extendedHeaderData.length; + } + + public int packFrames(byte[] bytes, int offset) throws NotSupportedException { + int newOffset = packSpecifiedFrames(bytes, offset, null, "APIC"); + newOffset = packSpecifiedFrames(bytes, newOffset, "APIC", null); + return newOffset; + } + + private int packSpecifiedFrames(byte[] bytes, int offset, String onlyId, String notId) throws NotSupportedException { + Iterator setIterator = frameSets.values().iterator(); + while (setIterator.hasNext()) { + ID3v2FrameSet frameSet = setIterator.next(); + if ((onlyId == null || onlyId.equals(frameSet.getId())) && (notId == null || !notId.equals(frameSet.getId()))) { + Iterator frameIterator = frameSet.getFrames().iterator(); + while (frameIterator.hasNext()) { + ID3v2Frame frame = (ID3v2Frame) frameIterator.next(); + if (frame.getDataLength() > 0) { + byte[] frameData = frame.toBytes(); + BufferTools.copyIntoByteBuffer(frameData, 0, frameData.length, bytes, offset); + offset += frameData.length; } } } + } + return offset; + } + + private int packFooter(byte[] bytes, int offset) { + try { + BufferTools.stringIntoByteBuffer(FOOTER_TAG, 0, FOOTER_TAG.length(), bytes, offset); + } catch (UnsupportedEncodingException e) { + } + String s[] = version.split("\\."); + if (s.length > 0) { + byte majorVersion = Byte.parseByte(s[0]); + bytes[offset + MAJOR_VERSION_OFFSET] = majorVersion; + } + if (s.length > 1) { + byte minorVersion = Byte.parseByte(s[1]); + bytes[offset + MINOR_VERSION_OFFSET] = minorVersion; + } + packFlags(bytes, offset); + BufferTools.packSynchsafeInteger(getDataLength(), bytes, offset + DATA_LENGTH_OFFSET); + return offset + FOOTER_LENGTH; + } + + private int calculateDataLength() { + int length = 0; + if (extendedHeader) { + length += extendedHeaderLength; + } + if (footer) { + length += FOOTER_LENGTH; + } else if (padding) { + length += PADDING_LENGTH; + } + Iterator setIterator = frameSets.values().iterator(); + while (setIterator.hasNext()) { + ID3v2FrameSet frameSet = setIterator.next(); + Iterator frameIterator = frameSet.getFrames().iterator(); + while (frameIterator.hasNext()) { + ID3v2Frame frame = (ID3v2Frame) frameIterator.next(); + length += frame.getLength(); + } + } + return length; + } + + protected boolean useFrameUnsynchronisation() { + return false; + } + + @Override + public String getVersion() { + return version; + } + + protected void invalidateDataLength() { + dataLength = 0; + } + + @Override + public int getDataLength() { + if (dataLength == 0) { + dataLength = calculateDataLength(); + } + return dataLength; + } + + @Override + public int getLength() { + return getDataLength() + HEADER_LENGTH; + } + + @Override + public Map getFrameSets() { + return frameSets; + } + + @Override + public boolean getPadding() { + return padding; + } + + @Override + public void setPadding(boolean padding) { + if (this.padding != padding) { + invalidateDataLength(); + this.padding = padding; + } + } + + @Override + public boolean hasFooter() { + return footer; + } + + @Override + public void setFooter(boolean footer) { + if (this.footer != footer) { + invalidateDataLength(); + this.footer = footer; + } + } + + @Override + public boolean hasUnsynchronisation() { + return unsynchronisation; + } + + @Override + public void setUnsynchronisation(boolean unsynchronisation) { + if (this.unsynchronisation != unsynchronisation) { + invalidateDataLength(); + this.unsynchronisation = unsynchronisation; + } + } + + @Override + public boolean getObseleteFormat() { + return obseleteFormat; + } + + @Override + public String getTrack() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_TRACK_OBSELETE : ID_TRACK); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setTrack(String track) { + if (track != null && track.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(track)); + addFrame(createFrame(ID_TRACK, frameData.toBytes()), true); + } + } + + @Override + public String getPartOfSet() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_PART_OF_SET_OBSELETE : ID_PART_OF_SET); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setPartOfSet(String partOfSet) { + if (partOfSet != null && partOfSet.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(partOfSet)); + addFrame(createFrame(ID_PART_OF_SET, frameData.toBytes()), true); + } + } + + @Override + public boolean isCompilation() { + // unofficial frame used by iTunes + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_COMPILATION_OBSELETE : ID_COMPILATION); + if (frameData != null && frameData.getText() != null) { + return "1".equals(frameData.getText().toString()); + } + return false; + } + + @Override + public void setCompilation(boolean compilation) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(compilation ? "1" : "0")); + addFrame(createFrame(ID_COMPILATION, frameData.toBytes()), true); + } + + @Override + public String getGrouping() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_GROUPING_OBSELETE : ID_GROUPING); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setGrouping(String grouping) { + if (grouping != null && grouping.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(grouping)); + addFrame(createFrame(ID_GROUPING, frameData.toBytes()), true); + } + } + + @Override + public String getArtist() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_ARTIST_OBSELETE : ID_ARTIST); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setArtist(String artist) { + if (artist != null && artist.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(artist)); + addFrame(createFrame(ID_ARTIST, frameData.toBytes()), true); + } + } + + @Override + public String getAlbumArtist() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_ALBUM_ARTIST_OBSELETE : ID_ALBUM_ARTIST); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setAlbumArtist(String albumArtist) { + if (albumArtist != null && albumArtist.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(albumArtist)); + addFrame(createFrame(ID_ALBUM_ARTIST, frameData.toBytes()), true); + } + } + + @Override + public String getTitle() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_TITLE_OBSELETE : ID_TITLE); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setTitle(String title) { + if (title != null && title.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(title)); + addFrame(createFrame(ID_TITLE, frameData.toBytes()), true); + } + } + + @Override + public String getAlbum() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_ALBUM_OBSELETE : ID_ALBUM); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setAlbum(String album) { + if (album != null && album.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(album)); + addFrame(createFrame(ID_ALBUM, frameData.toBytes()), true); + } + } + + @Override + public String getYear() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_YEAR_OBSELETE : ID_YEAR); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setYear(String year) { + if (year != null && year.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(year)); + addFrame(createFrame(ID_YEAR, frameData.toBytes()), true); + } + } + + @Override + public String getDate() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_DATE_OBSELETE : ID_DATE); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setDate(String date) { + if (date != null && date.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(date)); + addFrame(createFrame(ID_DATE, frameData.toBytes()), true); + } + } + + private int getGenre(String text) { + if (text != null && text.length() > 0) { + try { + return extractGenreNumber(text); + } catch (NumberFormatException e) { // match genre description + String description = extractGenreDescription(text); + return ID3v1Genres.matchGenreDescription(description); + } + } + return -1; + } + + @Override + public int getGenre() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_GENRE_OBSELETE : ID_GENRE); + if (frameData == null || frameData.getText() == null) { + return -1; + } + return getGenre(frameData.getText().toString()); + } + + @Override + public void setGenre(int genre) { + if (genre >= 0) { + invalidateDataLength(); + String genreDescription = genre < ID3v1Genres.GENRES.length ? ID3v1Genres.GENRES[genre] : ""; + String combinedGenre = "(" + Integer.toString(genre) + ")" + genreDescription; + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(combinedGenre)); + addFrame(createFrame(ID_GENRE, frameData.toBytes()), true); + } else { + // TODO remove frame? + } + } + + @Override + public int getBPM() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_BPM_OBSELETE : ID_BPM); + if (frameData == null || frameData.getText() == null) { + return -1; + } + String bpmStr = frameData.getText().toString(); + try { + return Integer.parseInt(bpmStr); + } catch (NumberFormatException e) { + // try float as some utilities add BPM like 67.8, or 67,8 + return (int) Float.parseFloat(bpmStr.trim().replaceAll(",", ".")); + } + } + + @Override + public void setBPM(int bpm) { + if (bpm >= 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(Integer.toString(bpm))); + addFrame(createFrame(ID_BPM, frameData.toBytes()), true); + } + } + + @Override + public String getKey() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_KEY_OBSELETE : ID_KEY); + if (frameData == null || frameData.getText() == null) { return null; } + return frameData.getText().toString(); + } + + @Override + public void setKey(String key) { + if (key != null && key.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(key)); + addFrame(createFrame(ID_KEY, frameData.toBytes()), true); + } + } - @Override - public String getLyrics() { - ID3v2CommentFrameData frameData; - if (obseleteFormat) - return null; - else - frameData = extractLyricsFrameData(ID_TEXT_LYRICS); - - if (frameData != null) - return frameData.getComment().toString(); - + @Override + public String getGenreDescription() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_GENRE_OBSELETE : ID_GENRE); + if (frameData == null || frameData.getText() == null) { return null; } - - @Override - public void setLyrics(String lyrics) { - if (lyrics != null && lyrics.length() > 0) { - invalidateDataLength(); - ID3v2CommentFrameData frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), "eng", null, new EncodedText(lyrics)); - addFrame(createFrame(ID_TEXT_LYRICS, frameData.toBytes()), true); + String text = frameData.getText().toString(); + if (text != null) { + int genreNum = getGenre(text); + if (genreNum >= 0 && genreNum < ID3v1Genres.GENRES.length) { + return ID3v1Genres.GENRES[genreNum]; + } else { + String description = extractGenreDescription(text); + if (description != null && description.length() > 0) { + return description; + } + } + } + return null; + } + + @Override + public void setGenreDescription(String text) throws IllegalArgumentException { + int genreNum = ID3v1Genres.matchGenreDescription(text); + if (genreNum < 0) { + throw new IllegalArgumentException("Unknown genre: " + text); + } + setGenre(genreNum); + } + + protected int extractGenreNumber(String genreValue) throws NumberFormatException { + String value = genreValue.trim(); + if (value.length() > 0) { + if (value.charAt(0) == '(') { + int pos = value.indexOf(')'); + if (pos > 0) { + return Integer.parseInt(value.substring(1, pos)); + } + } + } + return Integer.parseInt(value); + } + + protected String extractGenreDescription(String genreValue) throws NumberFormatException { + String value = genreValue.trim(); + if (value.length() > 0) { + if (value.charAt(0) == '(') { + int pos = value.indexOf(')'); + if (pos > 0) { + return value.substring(pos + 1); + } + } + return value; + } + return null; + } + + @Override + public String getComment() { + ID3v2CommentFrameData frameData = extractCommentFrameData(obseleteFormat ? ID_COMMENT_OBSELETE : ID_COMMENT, false); + if (frameData != null && frameData.getComment() != null) { + return frameData.getComment().toString(); + } + return null; + } + + @Override + public void setComment(String comment) { + if (comment != null && comment.length() > 0) { + invalidateDataLength(); + ID3v2CommentFrameData frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), "eng", null, new EncodedText(comment)); + addFrame(createFrame(ID_COMMENT, frameData.toBytes()), true); + } + } + + @Override + public String getItunesComment() { + ID3v2CommentFrameData frameData = extractCommentFrameData(obseleteFormat ? ID_COMMENT_OBSELETE : ID_COMMENT, true); + if (frameData != null && frameData.getComment() != null) { + return frameData.getComment().toString(); + } + return null; + } + + @Override + public void setItunesComment(String itunesComment) { + if (itunesComment != null && itunesComment.length() > 0) { + invalidateDataLength(); + ID3v2CommentFrameData frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), "eng", new EncodedText(ITUNES_COMMENT_DESCRIPTION), new EncodedText(itunesComment)); + addFrame(createFrame(ID_COMMENT, frameData.toBytes()), true); + } + } + + protected ID3v2CommentFrameData extractLyricsFrameData(String id) { + ID3v2FrameSet frameSet = frameSets.get(id); + if (frameSet != null) { + Iterator iterator = frameSet.getFrames().iterator(); + while (iterator.hasNext()) { + ID3v2Frame frame = (ID3v2Frame) iterator.next(); + ID3v2CommentFrameData frameData; + try { + frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), frame.getData()); + return frameData; + } catch (InvalidDataException e) { + // Do nothing + } } } + return null; + } + + @Override + public String getLyrics() { + ID3v2CommentFrameData frameData; + if (obseleteFormat) { + return null; + } else { + frameData = extractLyricsFrameData(ID_TEXT_LYRICS); + } + + if (frameData != null) { + return frameData.getComment().toString(); + } + + return null; + } + + @Override + public void setLyrics(String lyrics) { + if (lyrics != null && lyrics.length() > 0) { + invalidateDataLength(); + ID3v2CommentFrameData frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), "eng", null, new EncodedText(lyrics)); + addFrame(createFrame(ID_TEXT_LYRICS, frameData.toBytes()), true); + } + } + + @Override + public String getComposer() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_COMPOSER_OBSELETE : ID_COMPOSER); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setComposer(String composer) { + if (composer != null && composer.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(composer)); + addFrame(createFrame(ID_COMPOSER, frameData.toBytes()), true); + } + } + + @Override + public String getPublisher() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_PUBLISHER_OBSELETE : ID_PUBLISHER); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setPublisher(String publisher) { + if (publisher != null && publisher.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(publisher)); + addFrame(createFrame(ID_PUBLISHER, frameData.toBytes()), true); + } + } + + @Override + public String getOriginalArtist() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_ORIGINAL_ARTIST_OBSELETE : ID_ORIGINAL_ARTIST); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setOriginalArtist(String originalArtist) { + if (originalArtist != null && originalArtist.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(originalArtist)); + addFrame(createFrame(ID_ORIGINAL_ARTIST, frameData.toBytes()), true); + } + } + + @Override + public String getCopyright() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_COPYRIGHT_OBSELETE : ID_COPYRIGHT); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setCopyright(String copyright) { + if (copyright != null && copyright.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(copyright)); + addFrame(createFrame(ID_COPYRIGHT, frameData.toBytes()), true); + } + } + + @Override + public String getArtistUrl() { + ID3v2WWWFrameData frameData = extractWWWFrameData(ID_ARTIST_URL); + if (frameData != null) { + return frameData.getUrl(); + } + return null; + } + + @Override + public void setArtistUrl(String url) { + if (url != null && url.length() > 0) { + invalidateDataLength(); + ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); + addFrame(createFrame(ID_ARTIST_URL, frameData.toBytes()), true); + } + } + + @Override + public String getCommercialUrl() { + ID3v2WWWFrameData frameData = extractWWWFrameData(ID_COMMERCIAL_URL); + if (frameData != null) { + return frameData.getUrl(); + } + return null; + } + + @Override + public void setCommercialUrl(String url) { + if (url != null && url.length() > 0) { + invalidateDataLength(); + ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); + addFrame(createFrame(ID_COMMERCIAL_URL, frameData.toBytes()), true); + } + } + + @Override + public String getCopyrightUrl() { + ID3v2WWWFrameData frameData = extractWWWFrameData(ID_COPYRIGHT_URL); + if (frameData != null) { + return frameData.getUrl(); + } + return null; + } + + @Override + public void setCopyrightUrl(String url) { + if (url != null && url.length() > 0) { + invalidateDataLength(); + ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); + addFrame(createFrame(ID_COPYRIGHT_URL, frameData.toBytes()), true); + } + } - @Override - public String getComposer() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_COMPOSER_OBSELETE : ID_COMPOSER); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setComposer(String composer) { - if (composer != null && composer.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(composer)); - addFrame(createFrame(ID_COMPOSER, frameData.toBytes()), true); - } - } - - @Override - public String getPublisher() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_PUBLISHER_OBSELETE : ID_PUBLISHER); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setPublisher(String publisher) { - if (publisher != null && publisher.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(publisher)); - addFrame(createFrame(ID_PUBLISHER, frameData.toBytes()), true); - } - } - - @Override - public String getOriginalArtist() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_ORIGINAL_ARTIST_OBSELETE : ID_ORIGINAL_ARTIST); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setOriginalArtist(String originalArtist) { - if (originalArtist != null && originalArtist.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(originalArtist)); - addFrame(createFrame(ID_ORIGINAL_ARTIST, frameData.toBytes()), true); - } - } - - @Override - public String getCopyright() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_COPYRIGHT_OBSELETE : ID_COPYRIGHT); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setCopyright(String copyright) { - if (copyright != null && copyright.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(copyright)); - addFrame(createFrame(ID_COPYRIGHT, frameData.toBytes()), true); - } - } - - @Override - public String getArtistUrl() { - ID3v2WWWFrameData frameData = extractWWWFrameData(ID_ARTIST_URL); - if (frameData != null) return frameData.getUrl(); - return null; - } - - @Override - public void setArtistUrl(String url) { - if (url != null && url.length() > 0) { - invalidateDataLength(); - ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); - addFrame(createFrame(ID_ARTIST_URL, frameData.toBytes()), true); - } - } - - @Override - public String getCommercialUrl() { - ID3v2WWWFrameData frameData = extractWWWFrameData(ID_COMMERCIAL_URL); - if (frameData != null) return frameData.getUrl(); - return null; - } - - @Override - public void setCommercialUrl(String url) { - if (url != null && url.length() > 0) { - invalidateDataLength(); - ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); - addFrame(createFrame(ID_COMMERCIAL_URL, frameData.toBytes()), true); - } - } - - @Override - public String getCopyrightUrl() { - ID3v2WWWFrameData frameData = extractWWWFrameData(ID_COPYRIGHT_URL); - if (frameData != null) return frameData.getUrl(); - return null; - } - - @Override - public void setCopyrightUrl(String url) { - if (url != null && url.length() > 0) { - invalidateDataLength(); - ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); - addFrame(createFrame(ID_COPYRIGHT_URL, frameData.toBytes()), true); - } - } - - @Override - public String getAudiofileUrl() { - ID3v2WWWFrameData frameData = extractWWWFrameData(ID_AUDIOFILE_URL); - if (frameData != null) return frameData.getUrl(); - return null; - } - - @Override - public void setAudiofileUrl(String url) { - if (url != null && url.length() > 0) { - invalidateDataLength(); - ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); - addFrame(createFrame(ID_AUDIOFILE_URL, frameData.toBytes()), true); - } - } - - @Override - public String getAudioSourceUrl() { - ID3v2WWWFrameData frameData = extractWWWFrameData(ID_AUDIOSOURCE_URL); - if (frameData != null) return frameData.getUrl(); - return null; - } - - @Override - public void setAudioSourceUrl(String url) { - if (url != null && url.length() > 0) { - invalidateDataLength(); - ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); - addFrame(createFrame(ID_AUDIOSOURCE_URL, frameData.toBytes()), true); - } - } - - @Override - public String getRadiostationUrl() { - ID3v2WWWFrameData frameData = extractWWWFrameData(ID_RADIOSTATION_URL); - if (frameData != null) return frameData.getUrl(); - return null; - } - - @Override - public void setRadiostationUrl(String url) { - if (url != null && url.length() > 0) { - invalidateDataLength(); - ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); - addFrame(createFrame(ID_RADIOSTATION_URL, frameData.toBytes()), true); - } - } - - @Override - public String getPaymentUrl() { - ID3v2WWWFrameData frameData = extractWWWFrameData(ID_PAYMENT_URL); - if (frameData != null) return frameData.getUrl(); - return null; - } - - @Override - public void setPaymentUrl(String url) { - if (url != null && url.length() > 0) { - invalidateDataLength(); - ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); - addFrame(createFrame(ID_PAYMENT_URL, frameData.toBytes()), true); - } - } - - @Override - public String getPublisherUrl() { - ID3v2WWWFrameData frameData = extractWWWFrameData(ID_PUBLISHER_URL); - if (frameData != null) return frameData.getUrl(); - return null; - } - - @Override - public void setPublisherUrl(String url) { - if (url != null && url.length() > 0) { - invalidateDataLength(); - ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); - addFrame(createFrame(ID_PUBLISHER_URL, frameData.toBytes()), true); - } - } - - @Override - public String getUrl() { - ID3v2UrlFrameData frameData = extractUrlFrameData(obseleteFormat ? ID_URL_OBSELETE : ID_URL); - if (frameData != null) return frameData.getUrl(); - return null; - } - - @Override - public void setUrl(String url) { - if (url != null && url.length() > 0) { - invalidateDataLength(); - ID3v2UrlFrameData frameData = new ID3v2UrlFrameData(useFrameUnsynchronisation(), null, url); - addFrame(createFrame(ID_URL, frameData.toBytes()), true); - } - } - - @Override + @Override + public String getAudiofileUrl() { + ID3v2WWWFrameData frameData = extractWWWFrameData(ID_AUDIOFILE_URL); + if (frameData != null) { + return frameData.getUrl(); + } + return null; + } + + @Override + public void setAudiofileUrl(String url) { + if (url != null && url.length() > 0) { + invalidateDataLength(); + ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); + addFrame(createFrame(ID_AUDIOFILE_URL, frameData.toBytes()), true); + } + } + + @Override + public String getAudioSourceUrl() { + ID3v2WWWFrameData frameData = extractWWWFrameData(ID_AUDIOSOURCE_URL); + if (frameData != null) { + return frameData.getUrl(); + } + return null; + } + + @Override + public void setAudioSourceUrl(String url) { + if (url != null && url.length() > 0) { + invalidateDataLength(); + ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); + addFrame(createFrame(ID_AUDIOSOURCE_URL, frameData.toBytes()), true); + } + } + + @Override + public String getRadiostationUrl() { + ID3v2WWWFrameData frameData = extractWWWFrameData(ID_RADIOSTATION_URL); + if (frameData != null) { + return frameData.getUrl(); + } + return null; + } + + @Override + public void setRadiostationUrl(String url) { + if (url != null && url.length() > 0) { + invalidateDataLength(); + ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); + addFrame(createFrame(ID_RADIOSTATION_URL, frameData.toBytes()), true); + } + } + + @Override + public String getPaymentUrl() { + ID3v2WWWFrameData frameData = extractWWWFrameData(ID_PAYMENT_URL); + if (frameData != null) { + return frameData.getUrl(); + } + return null; + } + + @Override + public void setPaymentUrl(String url) { + if (url != null && url.length() > 0) { + invalidateDataLength(); + ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); + addFrame(createFrame(ID_PAYMENT_URL, frameData.toBytes()), true); + } + } + + @Override + public String getPublisherUrl() { + ID3v2WWWFrameData frameData = extractWWWFrameData(ID_PUBLISHER_URL); + if (frameData != null) { + return frameData.getUrl(); + } + return null; + } + + @Override + public void setPublisherUrl(String url) { + if (url != null && url.length() > 0) { + invalidateDataLength(); + ID3v2WWWFrameData frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), url); + addFrame(createFrame(ID_PUBLISHER_URL, frameData.toBytes()), true); + } + } + + @Override + public String getUrl() { + ID3v2UrlFrameData frameData = extractUrlFrameData(obseleteFormat ? ID_URL_OBSELETE : ID_URL); + if (frameData != null) { + return frameData.getUrl(); + } + return null; + } + + @Override + public void setUrl(String url) { + if (url != null && url.length() > 0) { + invalidateDataLength(); + ID3v2UrlFrameData frameData = new ID3v2UrlFrameData(useFrameUnsynchronisation(), null, url); + addFrame(createFrame(ID_URL, frameData.toBytes()), true); + } + } + + @Override public ArrayList getChapters() { if (obseleteFormat) { return null; @@ -976,14 +1041,14 @@ public ArrayList getChapters() { return extractChapterFrameData(ID_CHAPTER); } - - @Override + + @Override public void setChapters(ArrayList chapters) { - if(chapters != null) { + if (chapters != null) { invalidateDataLength(); boolean first = true; - for(ID3v2ChapterFrameData chapter: chapters) { - if(first) { + for (ID3v2ChapterFrameData chapter : chapters) { + if (first) { first = false; addFrame(createFrame(ID_CHAPTER, chapter.toBytes()), true); } else { @@ -993,7 +1058,7 @@ public void setChapters(ArrayList chapters) { } } - @Override + @Override public ArrayList getChapterTOC() { if (obseleteFormat) { return null; @@ -1001,14 +1066,14 @@ public ArrayList getChapterTOC() { return extractChapterTOCFrameData(ID_CHAPTER_TOC); } - - @Override + + @Override public void setChapterTOC(ArrayList toc) { - if(toc != null) { + if (toc != null) { invalidateDataLength(); boolean first = true; - for(ID3v2ChapterTOCFrameData ct: toc) { - if(first) { + for (ID3v2ChapterTOCFrameData ct : toc) { + if (first) { first = false; addFrame(createFrame(ID_CHAPTER_TOC, ct.toBytes()), true); } else { @@ -1018,84 +1083,113 @@ public void setChapterTOC(ArrayList toc) { } } - @Override - public String getEncoder() { - ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_ENCODER_OBSELETE: ID_ENCODER); - if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); - return null; - } - - @Override - public void setEncoder(String encoder) { - if (encoder != null && encoder.length() > 0) { - invalidateDataLength(); - ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(encoder)); - addFrame(createFrame(ID_ENCODER, frameData.toBytes()), true); - } - } - - @Override - public byte[] getAlbumImage() { - ID3v2PictureFrameData frameData = createPictureFrameData(obseleteFormat ? ID_IMAGE_OBSELETE : ID_IMAGE); - if (frameData != null) return frameData.getImageData(); - return null; - } - - @Override - public void setAlbumImage(byte[] albumImage, String mimeType) { - setAlbumImage(albumImage, mimeType, (byte) 0, null); - } - - @Override - public void setAlbumImage(byte[] albumImage, String mimeType, byte imageType, String imageDescription) { - if (albumImage != null && albumImage.length > 0 && mimeType != null && mimeType.length() > 0) { - invalidateDataLength(); - ID3v2PictureFrameData frameData = new ID3v2PictureFrameData( - useFrameUnsynchronisation(), mimeType, imageType, - null == imageDescription ? null : new EncodedText(imageDescription), - albumImage); - addFrame(createFrame(ID_IMAGE, frameData.toBytes()), true); - } - } - - @Override - public void clearAlbumImage() { - clearFrameSet(obseleteFormat ? ID_IMAGE_OBSELETE : ID_IMAGE); - } - - @Override - public String getAlbumImageMimeType() { - ID3v2PictureFrameData frameData = createPictureFrameData(obseleteFormat ? ID_IMAGE_OBSELETE : ID_IMAGE); - if (frameData != null && frameData.getMimeType() != null) return frameData.getMimeType(); - return null; - } - - @Override - public void clearFrameSet(String id) { - if (frameSets.remove(id) != null) { - invalidateDataLength(); - } - } - - - @Override - public int getWmpRating() { - final ID3v2PopmFrameData frameData = extractPopmFrameData(ID_RATING); - if (frameData != null && frameData.getAddress() != null) { - return frameData.getRating(); - } - return -1; + @Override + public String getEncoder() { + ID3v2TextFrameData frameData = extractTextFrameData(obseleteFormat ? ID_ENCODER_OBSELETE : ID_ENCODER); + if (frameData != null && frameData.getText() != null) { + return frameData.getText().toString(); + } + return null; + } + + @Override + public void setEncoder(String encoder) { + if (encoder != null && encoder.length() > 0) { + invalidateDataLength(); + ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText(encoder)); + addFrame(createFrame(ID_ENCODER, frameData.toBytes()), true); } + } - @Override - public void setWmpRating(final int rating) { - if (rating >= 0 && rating < 6) { - invalidateDataLength(); - final ID3v2PopmFrameData frameData = new ID3v2PopmFrameData(useFrameUnsynchronisation(), rating); - final byte[] bytes = frameData.toBytes(); - addFrame(createFrame(ID_RATING, bytes), true); - } - } + @Override + public byte[] getAlbumImage() { + ID3v2PictureFrameData frameData = createPictureFrameData(obseleteFormat ? ID_IMAGE_OBSELETE : ID_IMAGE); + if (frameData != null) { + return frameData.getImageData(); + } + return null; + } + + @Override + public void setAlbumImage(byte[] albumImage, String mimeType) { + setAlbumImage(albumImage, mimeType, (byte) 0, null); + } + + @Override + public void setAlbumImage(byte[] albumImage, String mimeType, byte imageType, String imageDescription) { + if (albumImage != null && albumImage.length > 0 && mimeType != null && mimeType.length() > 0) { + invalidateDataLength(); + ID3v2PictureFrameData frameData = new ID3v2PictureFrameData( + useFrameUnsynchronisation(), mimeType, imageType, + null == imageDescription ? null : new EncodedText(imageDescription), + albumImage); + addFrame(createFrame(ID_IMAGE, frameData.toBytes()), true); + } + } + + @Override + public void clearAlbumImage() { + clearFrameSet(obseleteFormat ? ID_IMAGE_OBSELETE : ID_IMAGE); + } + + @Override + public String getAlbumImageMimeType() { + ID3v2PictureFrameData frameData = createPictureFrameData(obseleteFormat ? ID_IMAGE_OBSELETE : ID_IMAGE); + if (frameData != null && frameData.getMimeType() != null) { + return frameData.getMimeType(); + } + return null; + } + + @Override + public void clearFrameSet(String id) { + if (frameSets.remove(id) != null) { + invalidateDataLength(); + } + } + + @Override + public int getWmpRating() { + final ID3v2PopmFrameData frameData = extractPopmFrameData(ID_RATING); + if (frameData != null && frameData.getAddress() != null) { + return frameData.getRating(); + } + return -1; + } + + @Override + public void setWmpRating(final int rating) { + if (rating >= 0 && rating < 6) { + invalidateDataLength(); + final ID3v2PopmFrameData frameData = new ID3v2PopmFrameData(useFrameUnsynchronisation(), rating); + final byte[] bytes = frameData.toBytes(); + addFrame(createFrame(ID_RATING, bytes), true); + } + } + + @Override + public String getCustomText(String description) { + ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( + frameSets, + unsynchronisation, + description); + + if (frameData != null) { + frameData.getValue().toString(); + } + + return null; + } + + @Override + public void setCustomText(String description, String value) { + ID3v2TXXXFrameData.createOrAddField( + frameSets, + unsynchronisation, + description, + value, + true); + } private ArrayList extractChapterFrameData(String id) { ID3v2FrameSet frameSet = frameSets.get(id); @@ -1136,125 +1230,156 @@ private ArrayList extractChapterTOCFrameData(String id } return null; } - - protected ID3v2TextFrameData extractTextFrameData(String id) { - ID3v2FrameSet frameSet = frameSets.get(id); - if (frameSet != null) { - ID3v2Frame frame = (ID3v2Frame) frameSet.getFrames().get(0); - ID3v2TextFrameData frameData; - try { - frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), frame.getData()); - return frameData; - } catch (InvalidDataException e) { - // do nothing - } - } - return null; - } - - private ID3v2WWWFrameData extractWWWFrameData(String id) { - ID3v2FrameSet frameSet = frameSets.get(id); - if (frameSet != null) { - ID3v2Frame frame = (ID3v2Frame) frameSet.getFrames().get(0); - ID3v2WWWFrameData frameData; - try { - frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), frame.getData()); - return frameData; - } catch (InvalidDataException e) { - // do nothing - } - } - return null; - } - - private ID3v2UrlFrameData extractUrlFrameData(String id) { - ID3v2FrameSet frameSet = frameSets.get(id); - if (frameSet != null) { - ID3v2Frame frame = (ID3v2Frame) frameSet.getFrames().get(0); - ID3v2UrlFrameData frameData; - try { - frameData = new ID3v2UrlFrameData(useFrameUnsynchronisation(), frame.getData()); - return frameData; - } catch (InvalidDataException e) { - // do nothing - } - } - return null; - } - - private ID3v2CommentFrameData extractCommentFrameData(String id, boolean itunes) { - ID3v2FrameSet frameSet = frameSets.get(id); - if (frameSet != null) { - Iterator iterator = frameSet.getFrames().iterator(); - while (iterator.hasNext()) { - ID3v2Frame frame = (ID3v2Frame) iterator.next(); - ID3v2CommentFrameData frameData; - try { - frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), frame.getData()); - if (itunes && ITUNES_COMMENT_DESCRIPTION.equals(frameData.getDescription().toString())) { - return frameData; - } else if (! itunes) { - return frameData; - } - } catch (InvalidDataException e) { - // Do nothing - } - } - } - return null; - } - - private ID3v2PictureFrameData createPictureFrameData(String id) { - ID3v2FrameSet frameSet = frameSets.get(id); - if (frameSet != null) { - ID3v2Frame frame = (ID3v2Frame) frameSet.getFrames().get(0); - ID3v2PictureFrameData frameData; - try { - if (obseleteFormat) frameData = new ID3v2ObseletePictureFrameData(useFrameUnsynchronisation(), frame.getData()); - else frameData = new ID3v2PictureFrameData(useFrameUnsynchronisation(), frame.getData()); - return frameData; - } catch (InvalidDataException e) { - // do nothing - } - } - return null; - } - - private ID3v2PopmFrameData extractPopmFrameData(final String id) { - final ID3v2FrameSet frameSet = frameSets.get(id); - if (frameSet != null) { - final ID3v2Frame frame = frameSet.getFrames().get(0); - ID3v2PopmFrameData frameData; - try { - frameData = new ID3v2PopmFrameData(useFrameUnsynchronisation(), frame.getData()); - return frameData; - } catch (final InvalidDataException e) { - // do nothing + + protected ID3v2TextFrameData extractTextFrameData(String id) { + ID3v2FrameSet frameSet = frameSets.get(id); + if (frameSet != null) { + ID3v2Frame frame = (ID3v2Frame) frameSet.getFrames().get(0); + ID3v2TextFrameData frameData; + try { + frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), frame.getData()); + return frameData; + } catch (InvalidDataException e) { + // do nothing + } + } + return null; + } + + private ID3v2WWWFrameData extractWWWFrameData(String id) { + ID3v2FrameSet frameSet = frameSets.get(id); + if (frameSet != null) { + ID3v2Frame frame = (ID3v2Frame) frameSet.getFrames().get(0); + ID3v2WWWFrameData frameData; + try { + frameData = new ID3v2WWWFrameData(useFrameUnsynchronisation(), frame.getData()); + return frameData; + } catch (InvalidDataException e) { + // do nothing + } + } + return null; + } + + private ID3v2UrlFrameData extractUrlFrameData(String id) { + ID3v2FrameSet frameSet = frameSets.get(id); + if (frameSet != null) { + ID3v2Frame frame = (ID3v2Frame) frameSet.getFrames().get(0); + ID3v2UrlFrameData frameData; + try { + frameData = new ID3v2UrlFrameData(useFrameUnsynchronisation(), frame.getData()); + return frameData; + } catch (InvalidDataException e) { + // do nothing + } + } + return null; + } + + private ID3v2CommentFrameData extractCommentFrameData(String id, boolean itunes) { + ID3v2FrameSet frameSet = frameSets.get(id); + if (frameSet != null) { + Iterator iterator = frameSet.getFrames().iterator(); + while (iterator.hasNext()) { + ID3v2Frame frame = (ID3v2Frame) iterator.next(); + ID3v2CommentFrameData frameData; + try { + frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), frame.getData()); + if (itunes && ITUNES_COMMENT_DESCRIPTION.equals(frameData.getDescription().toString())) { + return frameData; + } else if (!itunes) { + return frameData; } + } catch (InvalidDataException e) { + // Do nothing + } } - return null; - } - - @Override - public boolean equals(Object obj) { - if (! (obj instanceof AbstractID3v2Tag)) return false; - if (super.equals(obj)) return true; - AbstractID3v2Tag other = (AbstractID3v2Tag) obj; - if (unsynchronisation != other.unsynchronisation) return false; - if (extendedHeader != other.extendedHeader) return false; - if (experimental != other.experimental) return false; - if (footer != other.footer) return false; - if (compression != other.compression) return false; - if (dataLength != other.dataLength) return false; - if (extendedHeaderLength != other.extendedHeaderLength) return false; - if (version == null) { - if (other.version != null) return false; - } else if (other.version == null) return false; - else if (! version.equals(other.version)) return false; - if (frameSets == null) { - if (other.frameSets != null) return false; - } else if (other.frameSets == null) return false; - else if (! frameSets.equals(other.frameSets)) return false; - return true; - } + } + return null; + } + + private ID3v2PictureFrameData createPictureFrameData(String id) { + ID3v2FrameSet frameSet = frameSets.get(id); + if (frameSet != null) { + ID3v2Frame frame = (ID3v2Frame) frameSet.getFrames().get(0); + ID3v2PictureFrameData frameData; + try { + if (obseleteFormat) { + frameData = new ID3v2ObseletePictureFrameData(useFrameUnsynchronisation(), frame.getData()); + } else { + frameData = new ID3v2PictureFrameData(useFrameUnsynchronisation(), frame.getData()); + } + return frameData; + } catch (InvalidDataException e) { + // do nothing + } + } + return null; + } + + private ID3v2PopmFrameData extractPopmFrameData(final String id) { + final ID3v2FrameSet frameSet = frameSets.get(id); + if (frameSet != null) { + final ID3v2Frame frame = frameSet.getFrames().get(0); + ID3v2PopmFrameData frameData; + try { + frameData = new ID3v2PopmFrameData(useFrameUnsynchronisation(), frame.getData()); + return frameData; + } catch (final InvalidDataException e) { + // do nothing + } + } + return null; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof AbstractID3v2Tag)) { + return false; + } + if (super.equals(obj)) { + return true; + } + AbstractID3v2Tag other = (AbstractID3v2Tag) obj; + if (unsynchronisation != other.unsynchronisation) { + return false; + } + if (extendedHeader != other.extendedHeader) { + return false; + } + if (experimental != other.experimental) { + return false; + } + if (footer != other.footer) { + return false; + } + if (compression != other.compression) { + return false; + } + if (dataLength != other.dataLength) { + return false; + } + if (extendedHeaderLength != other.extendedHeaderLength) { + return false; + } + if (version == null) { + if (other.version != null) { + return false; + } + } else if (other.version == null) { + return false; + } else if (!version.equals(other.version)) { + return false; + } + if (frameSets == null) { + if (other.frameSets != null) { + return false; + } + } else if (other.frameSets == null) { + return false; + } else if (!frameSets.equals(other.frameSets)) { + return false; + } + return true; + } } diff --git a/src/main/java/com/mpatric/mp3agic/ID3v2.java b/src/main/java/com/mpatric/mp3agic/ID3v2.java index f840d506..1312a4bc 100644 --- a/src/main/java/com/mpatric/mp3agic/ID3v2.java +++ b/src/main/java/com/mpatric/mp3agic/ID3v2.java @@ -96,7 +96,10 @@ public interface ID3v2 extends ID3v1 { void setItunesComment(String itunesComment); String getLyrics(); - void setLyrics(String lyrics); + void setLyrics(String lyrics); + + String getCustomText(String description); + void setCustomText(String description, String value); /** * Set genre from text. diff --git a/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java b/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java index ff1595f8..a8addd72 100644 --- a/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java +++ b/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java @@ -1,6 +1,8 @@ package com.mpatric.mp3agic; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; public class ID3v2TXXXFrameData extends AbstractID3v2FrameData { @@ -40,8 +42,11 @@ protected void unpackFrameData(byte[] bytes) throws InvalidDataException { @Override protected byte[] packFrameData() { byte[] bytes = new byte[getLength()]; - if (value != null) bytes[0] = value.getTextEncoding(); - else bytes[0] = 0; + if (value != null) { + bytes[0] = value.getTextEncoding(); + } else { + bytes[0] = 0; + } int marker = 1; if (description != null) { byte[] descriptionBytes = description.toBytes(true, true); @@ -60,9 +65,14 @@ protected byte[] packFrameData() { @Override protected int getLength() { int length = 1; - if (description != null) length += description.toBytes(true, true).length; - else length++; - if (value != null) length += value.toBytes(true, false).length; + if (description != null) { + length += description.toBytes(true, true).length; + } else { + length++; + } + if (value != null) { + length += value.toBytes(true, false).length; + } return length; } @@ -93,45 +103,126 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (!super.equals(obj)) + } + if (!super.equals(obj)) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } return false; } - public static ArrayList extractAll(ID3v2FrameSet txxx, boolean useFrameUnsynchronisation, String d) { - if (txxx != null) { + public static Map extractAllFrames( + Map frameSets, + boolean useFrameUnsynchronisation, + String description) { + ID3v2FrameSet frameSet = frameSets.get(ID_FIELD); + + HashMap frames = new HashMap<>(); + + if (frameSet == null) + return frames; + + for (ID3v2Frame frame : frameSet.getFrames()) { + try { + ID3v2TXXXFrameData field = new ID3v2TXXXFrameData( + useFrameUnsynchronisation, + frame.getData()); + + if (description == null || field.getDescription().toString().contains(description)) { + frames.put(frame, field); + } + } catch (InvalidDataException e) { + e.printStackTrace(); + } + } - ArrayList fields = new ArrayList<>(); + return frames; + } - for (ID3v2Frame frame : txxx.getFrames()) - try { - ID3v2TXXXFrameData field = new ID3v2TXXXFrameData(useFrameUnsynchronisation, frame.getData()); + public static ArrayList extractAll( + Map frameSets, + boolean useFrameUnsynchronisation, + String description) { - if (d == null || field.getDescription().toString().contains(d)) - fields.add(field); - } catch (InvalidDataException e) { - // do nothing - } + Map frames = extractAllFrames( + frameSets, + useFrameUnsynchronisation, + description); - return fields; + ArrayList fields = new ArrayList<>(); + + for (Map.Entry item : frames.entrySet()) { + fields.add(item.getValue()); } - return null; + return fields; } - public static ArrayList extractAll(ID3v2FrameSet txxx, boolean useFrameUnsynchronisation) { - return extractAll(txxx, useFrameUnsynchronisation, null); + public static ArrayList extractAll( + Map frameSets, + boolean useFrameUnsynchronisation) { + + return extractAll(frameSets, useFrameUnsynchronisation, null); } - public static ID3v2TXXXFrameData extract(ID3v2FrameSet txxx, boolean useFrameUnsynchronisation, String d) { - ArrayList items = extractAll(txxx, useFrameUnsynchronisation, d); - if (items.size() > 0) + public static ID3v2TXXXFrameData extract( + Map frameSets, + boolean useFrameUnsynchronisation, + String description) { + + ArrayList items = extractAll( + frameSets, + useFrameUnsynchronisation, + description); + + if (items.size() > 0) { return items.get(0); + } return null; } + + public static void createOrAddField( + Map frameSets, + boolean useFrameUnsynchronisation, + String description, + String value, + boolean useDescriptionToMatch + ) { + + ID3v2TXXXFrameData field = new ID3v2TXXXFrameData( + useFrameUnsynchronisation, + new EncodedText(description), + new EncodedText(value)); + + ID3v2Frame frame = new ID3v2Frame( + ID_FIELD, + field.toBytes()); + + ID3v2FrameSet frameSet = frameSets.get(frame.getId()); + if (frameSet == null) { + frameSet = new ID3v2FrameSet(frame.getId()); + frameSet.addFrame(frame); + frameSets.put(frame.getId(), frameSet); + } else { + if (useDescriptionToMatch) { + Map frames = extractAllFrames( + frameSets, + useFrameUnsynchronisation, + description); + + if (frames != null && !frames.isEmpty()) { + frameSet.getFrames().removeAll(frames.keySet()); + } + } + + frameSet.addFrame(frame); + } + + } + } diff --git a/src/test/java/com/mpatric/mp3agic/ID3WrapperTest.java b/src/test/java/com/mpatric/mp3agic/ID3WrapperTest.java index 78033d32..2e20d4fe 100644 --- a/src/test/java/com/mpatric/mp3agic/ID3WrapperTest.java +++ b/src/test/java/com/mpatric/mp3agic/ID3WrapperTest.java @@ -1401,6 +1401,15 @@ public String getLyrics() { public void setLyrics(String lyrics) { this.lyrics = lyrics; } + + @Override + public String getCustomText(String description) { + return null; + } + + @Override + public void setCustomText(String description, String value) { + } @Override public void setGenreDescription(String text) { diff --git a/src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java b/src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java new file mode 100644 index 00000000..b313941d --- /dev/null +++ b/src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java @@ -0,0 +1,113 @@ +/* + * The MIT License + * + * Copyright 2017 Neeraj. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.mpatric.mp3agic; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author Neeraj + */ +public class ID3v2TXXXFrameDataTest { + + public ID3v2TXXXFrameDataTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @org.junit.Test + public void test() throws Exception { + System.out.println("test for txxx frame"); + + Map frameSets = new HashMap<>(); + + // for input of new field + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value", + true); + assertEquals(1, frameSets.size()); + + // for extraction + ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( + frameSets, + true, + "my_custom_text"); + assertEquals("my_custom_text", frameData.getDescription().toString()); + assertEquals("value", frameData.getValue().toString()); + + // for input with replacement + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value changed", + true); + frameData = ID3v2TXXXFrameData.extract( + frameSets, + true, + "my_custom_text"); + assertEquals("my_custom_text", frameData.getDescription().toString()); + assertEquals("value changed", frameData.getValue().toString()); + + // for input with-out replacement + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value 2", + false); + ArrayList frameDatas = ID3v2TXXXFrameData.extractAll( + frameSets, + true, + "my_custom_text"); + assertEquals(2, frameDatas.size()); + + } + +} From 4f68fc655bc2bc645644921e02621d3459f13f97 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Mon, 10 Apr 2017 22:47:32 +0200 Subject: [PATCH 4/9] resolve merge conflicts --- .../com/mpatric/mp3agic/AbstractID3v2Tag.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java b/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java index ec3c06b7..c98f4485 100644 --- a/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java +++ b/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java @@ -1096,6 +1096,30 @@ public void setWmpRating(final int rating) { } } + @Override + public String getCustomText(String description) { + ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( + frameSets, + unsynchronisation, + description); + + if (frameData != null) { + frameData.getValue().toString(); + } + + return null; + } + + @Override + public void setCustomText(String description, String value) { + ID3v2TXXXFrameData.createOrAddField( + frameSets, + unsynchronisation, + description, + value, + true); + } + private ArrayList extractChapterFrameData(String id) { ID3v2FrameSet frameSet = frameSets.get(id); if (frameSet != null) { From 051486f7d5c7d0c6d0dd9931b2a338b50f2b803e Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Mon, 10 Apr 2017 22:56:41 +0200 Subject: [PATCH 5/9] rm unused dependency --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index 5385b2e5..cf720a9f 100644 --- a/pom.xml +++ b/pom.xml @@ -49,12 +49,6 @@ 4.12 test - - org.hamcrest - hamcrest-core - 1.3 - test - From aaf7b5f9b3df9e92c35da8d1855cc156e0ab6763 Mon Sep 17 00:00:00 2001 From: Code0987 Date: Tue, 11 Apr 2017 23:53:15 +0530 Subject: [PATCH 6/9] updates tests --- .../mp3agic/ID3v2TXXXFrameDataTest.java | 216 +++++++++--------- 1 file changed, 102 insertions(+), 114 deletions(-) diff --git a/src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java b/src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java index fd29d934..06288a9f 100644 --- a/src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java +++ b/src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java @@ -1,114 +1,102 @@ -/* - * The MIT License - * - * Copyright 2017 Neeraj. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.mpatric.mp3agic; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * @author Neeraj - */ -public class ID3v2TXXXFrameDataTest { - - public ID3v2TXXXFrameDataTest() { - } - - @BeforeClass - public static void setUpClass() { - } - - @AfterClass - public static void tearDownClass() { - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - - @org.junit.Test - public void test() throws Exception { - System.out.println("test for txxx frame"); - - Map frameSets = new HashMap<>(); - - // for input of new field - ID3v2TXXXFrameData.createOrAddField( - frameSets, - true, - "my_custom_text", - "value", - true); - assertEquals(1, frameSets.size()); - - // for extraction - ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( - frameSets, - true, - "my_custom_text"); - assertEquals("my_custom_text", frameData.getDescription().toString()); - assertEquals("value", frameData.getValue().toString()); - - // for input with replacement - ID3v2TXXXFrameData.createOrAddField( - frameSets, - true, - "my_custom_text", - "value changed", - true); - frameData = ID3v2TXXXFrameData.extract( - frameSets, - true, - "my_custom_text"); - assertEquals("my_custom_text", frameData.getDescription().toString()); - assertEquals("value changed", frameData.getValue().toString()); - - // for input with-out replacement - ID3v2TXXXFrameData.createOrAddField( - frameSets, - true, - "my_custom_text", - "value 2", - false); - ArrayList frameDatas = ID3v2TXXXFrameData.extractAll( - frameSets, - true, - "my_custom_text"); - assertEquals(2, frameDatas.size()); - - } - -} +package com.mpatric.mp3agic; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class ID3v2TXXXFrameDataTest { + + @org.junit.Test + public void test_input() throws Exception { + Map frameSets = new HashMap<>(); + + // for input of new field + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value", + true); + + assertEquals(1, frameSets.size()); + } + + @org.junit.Test + public void test_extraction() throws Exception { + Map frameSets = new HashMap<>(); + + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value", + true); + + // for extraction + ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( + frameSets, + true, + "my_custom_text"); + + assertEquals("my_custom_text", frameData.getDescription().toString()); + assertEquals("value", frameData.getValue().toString()); + } + + @org.junit.Test + public void test_input_replacement() throws Exception { + Map frameSets = new HashMap<>(); + + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value", + true); + assertEquals(1, frameSets.size()); + + // for input with replacement + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value changed", + true); + ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( + frameSets, + true, + "my_custom_text"); + + assertEquals("my_custom_text", frameData.getDescription().toString()); + assertEquals("value changed", frameData.getValue().toString()); + } + + @org.junit.Test + public void test_input_no_replacement() throws Exception { + Map frameSets = new HashMap<>(); + + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value", + true); + assertEquals(1, frameSets.size()); + + // for input with-out replacement + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value 2", + false); + ArrayList frameDatas = ID3v2TXXXFrameData.extractAll( + frameSets, + true, + "my_custom_text"); + + assertEquals(2, frameDatas.size()); + } + +} From cdfc5235633d43c5093e6d2084f35e30388949f6 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Peters Date: Tue, 11 Apr 2017 21:18:30 +0200 Subject: [PATCH 7/9] cosmetique --- .../mp3agic/ID3v2TXXXFrameDataTest.java | 206 +++++++++--------- 1 file changed, 104 insertions(+), 102 deletions(-) diff --git a/src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java b/src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java index 06288a9f..0291e113 100644 --- a/src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java +++ b/src/test/java/com/mpatric/mp3agic/ID3v2TXXXFrameDataTest.java @@ -1,102 +1,104 @@ -package com.mpatric.mp3agic; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.*; - -public class ID3v2TXXXFrameDataTest { - - @org.junit.Test - public void test_input() throws Exception { - Map frameSets = new HashMap<>(); - - // for input of new field - ID3v2TXXXFrameData.createOrAddField( - frameSets, - true, - "my_custom_text", - "value", - true); - - assertEquals(1, frameSets.size()); - } - - @org.junit.Test - public void test_extraction() throws Exception { - Map frameSets = new HashMap<>(); - - ID3v2TXXXFrameData.createOrAddField( - frameSets, - true, - "my_custom_text", - "value", - true); - - // for extraction - ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( - frameSets, - true, - "my_custom_text"); - - assertEquals("my_custom_text", frameData.getDescription().toString()); - assertEquals("value", frameData.getValue().toString()); - } - - @org.junit.Test - public void test_input_replacement() throws Exception { - Map frameSets = new HashMap<>(); - - ID3v2TXXXFrameData.createOrAddField( - frameSets, - true, - "my_custom_text", - "value", - true); - assertEquals(1, frameSets.size()); - - // for input with replacement - ID3v2TXXXFrameData.createOrAddField( - frameSets, - true, - "my_custom_text", - "value changed", - true); - ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( - frameSets, - true, - "my_custom_text"); - - assertEquals("my_custom_text", frameData.getDescription().toString()); - assertEquals("value changed", frameData.getValue().toString()); - } - - @org.junit.Test - public void test_input_no_replacement() throws Exception { - Map frameSets = new HashMap<>(); - - ID3v2TXXXFrameData.createOrAddField( - frameSets, - true, - "my_custom_text", - "value", - true); - assertEquals(1, frameSets.size()); - - // for input with-out replacement - ID3v2TXXXFrameData.createOrAddField( - frameSets, - true, - "my_custom_text", - "value 2", - false); - ArrayList frameDatas = ID3v2TXXXFrameData.extractAll( - frameSets, - true, - "my_custom_text"); - - assertEquals(2, frameDatas.size()); - } - -} +package com.mpatric.mp3agic; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class ID3v2TXXXFrameDataTest { + + @Test + public void test_input() throws Exception { + Map frameSets = new HashMap<>(); + + // for input of new field + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value", + true); + + assertEquals(1, frameSets.size()); + } + + @Test + public void test_extraction() throws Exception { + Map frameSets = new HashMap<>(); + + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value", + true); + + // for extraction + ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( + frameSets, + true, + "my_custom_text"); + + assertEquals("my_custom_text", frameData.getDescription().toString()); + assertEquals("value", frameData.getValue().toString()); + } + + @Test + public void test_input_replacement() throws Exception { + Map frameSets = new HashMap<>(); + + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value", + true); + assertEquals(1, frameSets.size()); + + // for input with replacement + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value changed", + true); + ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( + frameSets, + true, + "my_custom_text"); + + assertEquals("my_custom_text", frameData.getDescription().toString()); + assertEquals("value changed", frameData.getValue().toString()); + } + + @Test + public void test_input_no_replacement() throws Exception { + Map frameSets = new HashMap<>(); + + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value", + true); + assertEquals(1, frameSets.size()); + + // for input with-out replacement + ID3v2TXXXFrameData.createOrAddField( + frameSets, + true, + "my_custom_text", + "value 2", + false); + ArrayList frameDatas = ID3v2TXXXFrameData.extractAll( + frameSets, + true, + "my_custom_text"); + + assertEquals(2, frameDatas.size()); + } + +} From 66bfa17fd424a1a9dbd12804efbb4af05cfb0ec1 Mon Sep 17 00:00:00 2001 From: Code0987 Date: Thu, 13 Apr 2017 11:59:33 +0530 Subject: [PATCH 8/9] tests updated bug fix --- .../java/com/mpatric/mp3agic/AbstractID3v2Tag.java | 2 +- src/test/java/com/mpatric/mp3agic/ID3v2TagTest.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java b/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java index c98f4485..3109a641 100644 --- a/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java +++ b/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java @@ -1104,7 +1104,7 @@ public String getCustomText(String description) { description); if (frameData != null) { - frameData.getValue().toString(); + return frameData.getValue().toString(); } return null; diff --git a/src/test/java/com/mpatric/mp3agic/ID3v2TagTest.java b/src/test/java/com/mpatric/mp3agic/ID3v2TagTest.java index 99e89348..98596c6b 100644 --- a/src/test/java/com/mpatric/mp3agic/ID3v2TagTest.java +++ b/src/test/java/com/mpatric/mp3agic/ID3v2TagTest.java @@ -516,8 +516,17 @@ public void shouldIgnoreInvalidWmpRatingOnWrite() throws Exception { final int invalidValue = 6; id3tag.setWmpRating(invalidValue); assertEquals(originalValue, id3tag.getWmpRating()); - } + } + + @Test + public void shouldGetSetCustomText() throws Exception { + Mp3File mp3File = new Mp3File("src/test/resources/v23tagwithchapters.mp3"); + ID3v2 id3tag = mp3File.getId3v2Tag(); + assertNull(id3tag.getCustomText("foo")); + id3tag.setCustomText("foo", "bar"); + assertEquals("bar", id3tag.getCustomText("foo")); + } private void setTagFields(ID3v2 id3tag) throws IOException { id3tag.setTrack("1"); From 663ba70449949de46a9ba4762a01c4bc9e7738ed Mon Sep 17 00:00:00 2001 From: Code0987 Date: Fri, 2 Jun 2017 01:34:57 +0530 Subject: [PATCH 9/9] updated --- .../com/mpatric/mp3agic/AbstractID3v2Tag.java | 4 +- .../mpatric/mp3agic/ID3v2TXXXFrameData.java | 453 +++++++++--------- 2 files changed, 237 insertions(+), 220 deletions(-) diff --git a/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java b/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java index 3109a641..8893fc4d 100644 --- a/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java +++ b/src/main/java/com/mpatric/mp3agic/AbstractID3v2Tag.java @@ -1098,8 +1098,8 @@ public void setWmpRating(final int rating) { @Override public String getCustomText(String description) { - ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( - frameSets, + ID3v2TXXXFrameData frameData = ID3v2TXXXFrameData.extract( + frameSets, unsynchronisation, description); diff --git a/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java b/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java index e4488cbb..ae998fd5 100644 --- a/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java +++ b/src/main/java/com/mpatric/mp3agic/ID3v2TXXXFrameData.java @@ -1,228 +1,245 @@ package com.mpatric.mp3agic; +import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.Objects; public class ID3v2TXXXFrameData extends AbstractID3v2FrameData { - protected static final String TAG = "ID3::TXXX"; - - public static final String ID_FIELD = "TXXX"; - - private EncodedText description; - private EncodedText value; - - public ID3v2TXXXFrameData(boolean unsynchronisation) { - super(unsynchronisation); - } - - public ID3v2TXXXFrameData(boolean unsynchronisation, EncodedText description, EncodedText value) { - super(unsynchronisation); - this.description = description; - this.value = value; - } - - public ID3v2TXXXFrameData(boolean unsynchronisation, byte[] bytes) throws InvalidDataException { - super(unsynchronisation); - synchroniseAndUnpackFrameData(bytes); - } - - @Override - protected void unpackFrameData(byte[] bytes) throws InvalidDataException { - int marker = BufferTools.indexOfTerminatorForEncoding(bytes, 1, bytes[0]); - - description = new EncodedText(bytes[0], BufferTools.copyBuffer(bytes, 1, marker - 1)); - - marker += description.getTerminator().length; - - value = new EncodedText(bytes[0], BufferTools.copyBuffer(bytes, marker, bytes.length - marker)); - } - - @Override - protected byte[] packFrameData() { - byte[] bytes = new byte[getLength()]; - if (value != null) { - bytes[0] = value.getTextEncoding(); - } else { - bytes[0] = 0; - } - int marker = 1; - if (description != null) { - byte[] descriptionBytes = description.toBytes(true, true); - BufferTools.copyIntoByteBuffer(descriptionBytes, 0, descriptionBytes.length, bytes, marker); - marker += descriptionBytes.length; - } else { - bytes[marker++] = 0; - } - if (value != null) { - byte[] commentBytes = value.toBytes(true, false); - BufferTools.copyIntoByteBuffer(commentBytes, 0, commentBytes.length, bytes, marker); - } - return bytes; - } - - @Override - protected int getLength() { - int length = 1; - if (description != null) { - length += description.toBytes(true, true).length; - } else { - length++; - } - if (value != null) { - length += value.toBytes(true, false).length; - } - return length; - } - - public EncodedText getValue() { - return value; - } - - public void setValue(EncodedText value) { - this.value = value; - } - - public EncodedText getDescription() { - return description; - } - - public void setDescription(EncodedText description) { - this.description = description; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((value == null) ? 0 : value.hashCode()); - result = prime * result + ((description == null) ? 0 : description.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!super.equals(obj)) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - return false; - } - - public static Map extractAllFrames( - Map frameSets, - boolean useFrameUnsynchronisation, - String description) { - ID3v2FrameSet frameSet = frameSets.get(ID_FIELD); - - HashMap frames = new HashMap<>(); - - if (frameSet == null) - return frames; - - for (ID3v2Frame frame : frameSet.getFrames()) { - try { - ID3v2TXXXFrameData field = new ID3v2TXXXFrameData( - useFrameUnsynchronisation, - frame.getData()); - - if (description == null || field.getDescription().toString().contains(description)) { - frames.put(frame, field); - } - } catch (InvalidDataException e) { - e.printStackTrace(); - } - } - - return frames; - } - - public static ArrayList extractAll( - Map frameSets, - boolean useFrameUnsynchronisation, - String description) { - - Map frames = extractAllFrames( - frameSets, - useFrameUnsynchronisation, - description); - - ArrayList fields = new ArrayList<>(); - - for (Map.Entry item : frames.entrySet()) { - fields.add(item.getValue()); - } - - return fields; - } - - public static ArrayList extractAll( - Map frameSets, - boolean useFrameUnsynchronisation) { - - return extractAll(frameSets, useFrameUnsynchronisation, null); - } - - public static ID3v2TXXXFrameData extract( - Map frameSets, - boolean useFrameUnsynchronisation, - String description) { - - ArrayList items = extractAll( - frameSets, - useFrameUnsynchronisation, - description); - - if (items.size() > 0) { - return items.get(0); - } - - return null; - } - - public static void createOrAddField( - Map frameSets, - boolean useFrameUnsynchronisation, - String description, - String value, - boolean useDescriptionToMatch - ) { - - ID3v2TXXXFrameData field = new ID3v2TXXXFrameData( - useFrameUnsynchronisation, - new EncodedText(description), - new EncodedText(value)); - - ID3v2Frame frame = new ID3v2Frame( - ID_FIELD, - field.toBytes()); - - ID3v2FrameSet frameSet = frameSets.get(frame.getId()); - if (frameSet == null) { - frameSet = new ID3v2FrameSet(frame.getId()); - frameSet.addFrame(frame); - frameSets.put(frame.getId(), frameSet); - } else { - if (useDescriptionToMatch) { - Map frames = extractAllFrames( - frameSets, - useFrameUnsynchronisation, - description); - - if (frames != null && !frames.isEmpty()) { - frameSet.getFrames().removeAll(frames.keySet()); - } - } - - frameSet.addFrame(frame); - } - - } + protected static final String TAG = "ID3::TXXX"; + + public static final String ID_FIELD = "TXXX"; + + private EncodedText description; + private EncodedText value; + + public ID3v2TXXXFrameData(boolean unsynchronisation) { + super(unsynchronisation); + } + + public ID3v2TXXXFrameData(boolean unsynchronisation, EncodedText description, EncodedText value) { + super(unsynchronisation); + this.description = description; + this.value = value; + } + + public ID3v2TXXXFrameData(boolean unsynchronisation, byte[] bytes) throws InvalidDataException { + super(unsynchronisation); + synchroniseAndUnpackFrameData(bytes); + } + + @Override + protected void unpackFrameData(byte[] bytes) throws InvalidDataException { + int marker = BufferTools.indexOfTerminatorForEncoding(bytes, 1, bytes[0]); + if (marker >= 0) { + description = new EncodedText(bytes[0], BufferTools.copyBuffer(bytes, 1, marker - 1)); + marker += description.getTerminator().length; + } else { + description = new EncodedText(bytes[0], ""); + marker = 1; + } + + if (bytes.length - marker >= 0) { + value = new EncodedText(bytes[0], BufferTools.copyBuffer(bytes, marker, bytes.length - marker)); + } else { + value = new EncodedText(bytes[0], ""); + } + } + + @Override + protected byte[] packFrameData() { + byte[] bytes = new byte[getLength()]; + if (value != null) { + bytes[0] = value.getTextEncoding(); + } else { + bytes[0] = 0; + } + int marker = 1; + if (description != null) { + byte[] descriptionBytes = description.toBytes(true, true); + BufferTools.copyIntoByteBuffer(descriptionBytes, 0, descriptionBytes.length, bytes, marker); + marker += descriptionBytes.length; + } else { + bytes[marker++] = 0; + } + if (value != null) { + byte[] commentBytes = value.toBytes(true, false); + BufferTools.copyIntoByteBuffer(commentBytes, 0, commentBytes.length, bytes, marker); + } + return bytes; + } + + @Override + protected int getLength() { + int length = 1; + if (description != null) { + length += description.toBytes(true, true).length; + } else { + length++; + } + if (value != null) { + length += value.toBytes(true, false).length; + } + return length; + } + + public EncodedText getValue() { + return value; + } + + public void setValue(EncodedText value) { + this.value = value; + } + + public EncodedText getDescription() { + return description; + } + + public void setDescription(EncodedText description) { + this.description = description; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + result = prime * result + ((description == null) ? 0 : description.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ID3v2TXXXFrameData other = (ID3v2TXXXFrameData) obj; + if (!Objects.equals(this.description, other.description)) { + return false; + } + if (!Objects.equals(this.value, other.value)) { + return false; + } + return true; + } + + public static Map extractAllFrames( + Map frameSets, + boolean useFrameUnsynchronisation, + String description) { + ID3v2FrameSet frameSet = frameSets.get(ID_FIELD); + + HashMap frames = new HashMap<>(); + + if (frameSet == null) { + return frames; + } + + for (ID3v2Frame frame : frameSet.getFrames()) { + try { + ID3v2TXXXFrameData field = new ID3v2TXXXFrameData( + useFrameUnsynchronisation, + frame.getData()); + + if (description == null || field.getDescription().toString().contains(description)) { + frames.put(frame, field); + } + } catch (InvalidDataException e) { + e.printStackTrace(); + } + } + + return frames; + } + + public static ArrayList extractAll( + Map frameSets, + boolean useFrameUnsynchronisation, + String description) { + + Map frames = extractAllFrames( + frameSets, + useFrameUnsynchronisation, + description); + + ArrayList fields = new ArrayList<>(); + + for (Map.Entry item : frames.entrySet()) { + fields.add(item.getValue()); + } + + return fields; + } + + public static ArrayList extractAll( + Map frameSets, + boolean useFrameUnsynchronisation) { + + return extractAll(frameSets, useFrameUnsynchronisation, null); + } + + public static ID3v2TXXXFrameData extract( + Map frameSets, + boolean useFrameUnsynchronisation, + String description) { + + ArrayList items = extractAll( + frameSets, + useFrameUnsynchronisation, + description); + + if (items.size() > 0) { + return items.get(0); + } + + return null; + } + + public static void createOrAddField( + Map frameSets, + boolean useFrameUnsynchronisation, + String description, + String value, + boolean useDescriptionToMatch + ) { + + ID3v2TXXXFrameData field = new ID3v2TXXXFrameData( + useFrameUnsynchronisation, + new EncodedText(description), + new EncodedText(value)); + + ID3v2Frame frame = new ID3v2Frame( + ID_FIELD, + field.toBytes()); + + ID3v2FrameSet frameSet = frameSets.get(frame.getId()); + if (frameSet == null) { + frameSet = new ID3v2FrameSet(frame.getId()); + frameSet.addFrame(frame); + frameSets.put(frame.getId(), frameSet); + } else { + if (useDescriptionToMatch) { + Map frames = extractAllFrames( + frameSets, + useFrameUnsynchronisation, + description); + + if (frames != null && !frames.isEmpty()) { + frameSet.getFrames().removeAll(frames.keySet()); + } + } + + frameSet.addFrame(frame); + } + + } }