package com.frostwire.mp3; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; 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_COPYRIGHT = "TCOP"; public static final String ID_ORIGINAL_ARTIST = "TOPE"; public static final String ID_COMPOSER = "TCOM"; public static final String ID_COMMENT = "COMM"; public static final String ID_GENRE = "TCON"; public static final String ID_YEAR = "TYER"; public static final String ID_ALBUM = "TALB"; public static final String ID_TITLE = "TIT2"; public static final String ID_ARTIST = "TPE1"; public static final String ID_TRACK = "TRCK"; 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_COMPOSER_OBSELETE = "TCM"; 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_ALBUM_OBSELETE = "TAL"; public static final String ID_TITLE_OBSELETE = "TT2"; public static final String ID_ARTIST_OBSELETE = "TP1"; public static final String ID_TRACK_OBSELETE = "TRK"; 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<String, ID3v2FrameSet> frameSets; public AbstractID3v2Tag() { frameSets = new TreeMap<String, ID3v2FrameSet>(); } public AbstractID3v2Tag(byte[] bytes) throws NoSuchTagException, UnsupportedTagException, InvalidDataException { this(bytes, false); } public AbstractID3v2Tag(byte[] bytes, boolean obseleteFormat) throws NoSuchTagException, UnsupportedTagException, InvalidDataException { frameSets = new TreeMap<String, ID3v2FrameSet>(); 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; } private 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.byteBufferToString(bytes, offset, FOOTER_TAG.length()))) { throw new InvalidDataException("Invalid footer"); } return FOOTER_LENGTH; } 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) { BufferTools.stringIntoByteBuffer(TAG, 0, TAG.length(), bytes, offset); 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<ID3v2FrameSet> 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<ID3v2Frame> 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) { BufferTools.stringIntoByteBuffer(FOOTER_TAG, 0, FOOTER_TAG.length(), bytes, offset); 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[0]); 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<ID3v2FrameSet> setIterator = frameSets.values().iterator(); while (setIterator.hasNext()) { ID3v2FrameSet frameSet = setIterator.next(); Iterator<ID3v2Frame> frameIterator = frameSet.getFrames().iterator(); while (frameIterator.hasNext()) { ID3v2Frame frame = (ID3v2Frame) frameIterator.next(); length += frame.getLength(); } } return length; } protected boolean useFrameUnsynchronisation() { return false; } public String getVersion() { return version; } private void invalidateDataLength() { dataLength = 0; } public int getDataLength() { if (dataLength == 0) { dataLength = calculateDataLength(); } return dataLength; } public int getLength() { return getDataLength() + HEADER_LENGTH; } public Map<String, ID3v2FrameSet> getFrameSets() { return frameSets; } public boolean getPadding() { return padding; } public void setPadding(boolean padding) { if (this.padding != padding) { invalidateDataLength(); this.padding = padding; } } public boolean hasFooter() { return footer; } public void setFooter(boolean footer) { if (this.footer != footer) { invalidateDataLength(); this.footer = footer; } } public boolean hasUnsynchronisation() { return unsynchronisation; } public void setUnsynchronisation(boolean unsynchronisation) { if (this.unsynchronisation != unsynchronisation) { invalidateDataLength(); this.unsynchronisation = unsynchronisation; } } public boolean getObseleteFormat() { return obseleteFormat; } public String getTrack() { ID3v2TextFrameData frameData; if (obseleteFormat) frameData = extractTextFrameData(ID_TRACK_OBSELETE); else frameData = extractTextFrameData(ID_TRACK); if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); return null; } public void setTrack(String track) { if (track != null && track.length() > 0) { invalidateDataLength(); ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText((byte)0, track)); addFrame(createFrame(ID_TRACK, frameData.toBytes()), true); } } public String getArtist() { ID3v2TextFrameData frameData; if (obseleteFormat) frameData = extractTextFrameData(ID_ARTIST_OBSELETE); else frameData = extractTextFrameData(ID_ARTIST); if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); return null; } public void setArtist(String artist) { if (artist != null && artist.length() > 0) { invalidateDataLength(); ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText((byte)0, artist)); addFrame(createFrame(ID_ARTIST, frameData.toBytes()), true); } } public String getTitle() { ID3v2TextFrameData frameData; if (obseleteFormat) frameData = extractTextFrameData(ID_TITLE_OBSELETE); else frameData = extractTextFrameData(ID_TITLE); if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); return null; } public void setTitle(String title) { if (title != null && title.length() > 0) { invalidateDataLength(); ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText((byte)0, title)); addFrame(createFrame(ID_TITLE, frameData.toBytes()), true); } } public String getAlbum() { ID3v2TextFrameData frameData; if (obseleteFormat) frameData = extractTextFrameData(ID_ALBUM_OBSELETE); else frameData = extractTextFrameData(ID_ALBUM); if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); return null; } public void setAlbum(String album) { if (album != null && album.length() > 0) { invalidateDataLength(); ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText((byte)0, album)); addFrame(createFrame(ID_ALBUM, frameData.toBytes()), true); } } public String getYear() { ID3v2TextFrameData frameData; if (obseleteFormat) frameData = extractTextFrameData(ID_YEAR_OBSELETE); else frameData = extractTextFrameData(ID_YEAR); if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); return null; } public void setYear(String year) { if (year != null && year.length() > 0) { invalidateDataLength(); ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText((byte)0, year)); addFrame(createFrame(ID_YEAR, frameData.toBytes()), true); } } public int getGenre() { ID3v2TextFrameData frameData; if (obseleteFormat) frameData = extractTextFrameData(ID_GENRE_OBSELETE); else frameData = extractTextFrameData(ID_GENRE); if (frameData == null || frameData.getText() == null) return -1; String text = frameData.getText().toString(); if (text == null || text.length() == 0) return -1; try { return extractGenreNumber(text); } catch (NumberFormatException e) { String description = extractGenreDescription(text); if (description != null && description.length() > 0) { for (int i = 0; i < ID3v1Genres.GENRES.length; i++) { if (ID3v1Genres.GENRES[i].compareToIgnoreCase(description) == 0) return i; } } } return -1; } public void setGenre(int genre) { if (genre >= 0) { invalidateDataLength(); String genreDescription; try { genreDescription = ID3v1Genres.GENRES[genre]; } catch (ArrayIndexOutOfBoundsException e) { genreDescription = ""; } String combinedGenre = "(" + Integer.toString(genre) + ")" + genreDescription; ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText((byte)0, combinedGenre)); addFrame(createFrame(ID_GENRE, frameData.toBytes()), true); } } public String getGenreDescription() { int genreNum = getGenre(); if (genreNum >= 0) { try { return ID3v1Genres.GENRES[genreNum]; } catch (ArrayIndexOutOfBoundsException e) { return null; } } ID3v2TextFrameData frameData; if (obseleteFormat) frameData = extractTextFrameData(ID_GENRE_OBSELETE); else frameData = extractTextFrameData(ID_GENRE); if (frameData != null && frameData.getText() != null) { String text = frameData.getText().toString(); if (text != null && text.length() > 0) { String description = extractGenreDescription(text); if (description != null & description.length() > 0) { return description; } } } return null; } 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; } public String getComment() { ID3v2CommentFrameData frameData; if (obseleteFormat) frameData = extractCommentFrameData(ID_COMMENT_OBSELETE, false); else frameData = extractCommentFrameData(ID_COMMENT, false); if (frameData != null && frameData.getComment() != null) return frameData.getComment().toString(); return null; } public void setComment(String comment) { if (comment != null && comment.length() > 0) { invalidateDataLength(); ID3v2CommentFrameData frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), "eng", null, new EncodedText((byte)0, comment)); addFrame(createFrame(ID_COMMENT, frameData.toBytes()), true); } } public String getItunesComment() { ID3v2CommentFrameData frameData; if (obseleteFormat) frameData = extractCommentFrameData(ID_COMMENT_OBSELETE, true); else frameData = extractCommentFrameData(ID_COMMENT, true); if (frameData != null && frameData.getComment() != null) return frameData.getComment().toString(); return null; } public void setItunesComment(String itunesComment) { if (itunesComment != null && itunesComment.length() > 0) { invalidateDataLength(); ID3v2CommentFrameData frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), ITUNES_COMMENT_DESCRIPTION, null, new EncodedText((byte)0, itunesComment)); addFrame(createFrame(ID_COMMENT, frameData.toBytes()), true); } } public String getComposer() { ID3v2TextFrameData frameData; if (obseleteFormat) frameData = extractTextFrameData(ID_COMPOSER_OBSELETE); else frameData = extractTextFrameData(ID_COMPOSER); if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); return null; } public void setComposer(String composer) { if (composer != null && composer.length() > 0) { invalidateDataLength(); ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText((byte)0, composer)); addFrame(createFrame(ID_COMPOSER, frameData.toBytes()), true); } } public String getOriginalArtist() { ID3v2TextFrameData frameData; if (obseleteFormat) frameData = extractTextFrameData(ID_ORIGINAL_ARTIST_OBSELETE); else frameData = extractTextFrameData(ID_ORIGINAL_ARTIST); if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); return null; } public void setOriginalArtist(String originalArtist) { if (originalArtist != null && originalArtist.length() > 0) { invalidateDataLength(); ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText((byte)0, originalArtist)); addFrame(createFrame(ID_ORIGINAL_ARTIST, frameData.toBytes()), true); } } public String getCopyright() { ID3v2TextFrameData frameData; if (obseleteFormat) frameData = extractTextFrameData(ID_COPYRIGHT_OBSELETE); else frameData = extractTextFrameData(ID_COPYRIGHT); if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); return null; } public void setCopyright(String copyright) { if (copyright != null && copyright.length() > 0) { invalidateDataLength(); ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText((byte)0, copyright)); addFrame(createFrame(ID_COPYRIGHT, frameData.toBytes()), true); } } public String getUrl() { ID3v2UrlFrameData frameData; if (obseleteFormat) frameData = extractUrlFrameData(ID_URL_OBSELETE); else frameData = extractUrlFrameData(ID_URL); if (frameData != null) return frameData.getUrl(); return null; } 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); } } public String getEncoder() { ID3v2TextFrameData frameData; if (obseleteFormat) frameData = extractTextFrameData(ID_ENCODER_OBSELETE); else frameData = extractTextFrameData(ID_ENCODER); if (frameData != null && frameData.getText() != null) return frameData.getText().toString(); return null; } public void setEncoder(String encoder) { if (encoder != null && encoder.length() > 0) { invalidateDataLength(); ID3v2TextFrameData frameData = new ID3v2TextFrameData(useFrameUnsynchronisation(), new EncodedText((byte)0, encoder)); addFrame(createFrame(ID_ENCODER, frameData.toBytes()), true); } } public byte[] getAlbumImage() { ID3v2PictureFrameData frameData; if (obseleteFormat) frameData = createPictureFrameData(ID_IMAGE_OBSELETE); else frameData = createPictureFrameData(ID_IMAGE); if (frameData != null) return frameData.getImageData(); return null; } public void setAlbumImage(byte[] albumImage, String mimeType) { if (albumImage != null && albumImage.length > 0 && mimeType != null && mimeType.length() > 0) { invalidateDataLength(); ID3v2PictureFrameData frameData = new ID3v2PictureFrameData(useFrameUnsynchronisation(), mimeType, (byte)0, null, albumImage); addFrame(createFrame(ID_IMAGE, frameData.toBytes()), true); } } public String getAlbumImageMimeType() { ID3v2PictureFrameData frameData; if (obseleteFormat) frameData = createPictureFrameData(ID_IMAGE_OBSELETE); else frameData = createPictureFrameData(ID_IMAGE); if (frameData != null && frameData.getMimeType() != null) return frameData.getMimeType(); return null; } public void clearFrameSet(String id) { if (frameSets.remove(id) != null) { invalidateDataLength(); } } private 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 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<ID3v2Frame> iterator = frameSet.getFrames().iterator(); while (iterator.hasNext()) { ID3v2Frame frame = (ID3v2Frame) iterator.next(); ID3v2CommentFrameData frameData; try { frameData = new ID3v2CommentFrameData(useFrameUnsynchronisation(), frame.getData()); if (!itunes && frameData.getDescription().toString().startsWith("iTun")) { continue; } 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; } 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; } }