/* * ID3V2Tag.java * * Created on 24-Nov-2003 * * Copyright (C)2003-2005 Paul Grebenc * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id: ID3V2Tag.java,v 1.23 2005/10/27 02:12:20 paul Exp $ */ package org.blinkenlights.jid3.v2; import java.io.*; import java.util.*; import org.blinkenlights.jid3.*; import org.blinkenlights.jid3.io.*; /** * @author paul * * Base class representing all ID3 V2 tags. */ abstract public class ID3V2Tag extends ID3Tag { /** Flag indicating whether unsynchronization is used in this tag or not. */ protected boolean m_bUnsynchronizationUsedFlag; /** Flag indicating whether the extended header is present or not. */ protected boolean m_bExtendedHeaderFlag; /** Flag indicating this tag should be considered experimental. */ protected boolean m_bExperimentalFlag; /** Flag indicating whether a CRC value exists in the extended header. */ protected boolean m_bCRCDataFlag; /** Mapping from frame ID to list containing frames. For frames that can only be used once. */ protected Map m_oFrameIdToFrameMap = null; /** Default padding for ID3 v2 frames, if not specified. 16 bytes, because Winamp does not read the last * frame when there isn't at least 6 bytes of padding following it in a tag. */ private static int s_iDefaultPaddingLength = 16; /** Value specifying the amount of padding which is appended to the frames in this tag. */ protected int m_iPaddingLength; /** Construct an ID3 V2 tag, specifying flag values. * * @param bUnsynchronizationUsedFlag specify whether unsynchronization is to be used in this tag or not * @param bExtendedHeaderFlag specify whether the extended header will be present or not * @param bExperimentalFlag specify whether this tag is to be considered experimental or not */ public ID3V2Tag(boolean bUnsynchronizationUsedFlag, boolean bExtendedHeaderFlag, boolean bExperimentalFlag) { m_bUnsynchronizationUsedFlag = bUnsynchronizationUsedFlag; m_bExtendedHeaderFlag = bExtendedHeaderFlag; m_bExperimentalFlag = bExperimentalFlag; m_oFrameIdToFrameMap = new HashMap(); //HACK: Default padding of 16 bytes, because Winamp doesn't seem to see the last frame in a v2 tag // when there is less than 6 bytes of padding. (???) m_iPaddingLength = s_iDefaultPaddingLength; } /** Get all frames set in this tag which can only be stored once in the tag. * This method exists to aid in testing. */ public ID3V2Frame[] getSingleFrames() { return (ID3V2Frame[])m_oFrameIdToFrameMap.values().toArray(new ID3V2Frame[0]); } /** Check if this tag contains at least one frame. An ID3V2 tag requires at least one frame to be written. * * @return true if this tag contains at least one frame, and false otherwise */ abstract public boolean containsAtLeastOneFrame(); abstract public void sanityCheck() throws ID3Exception; /** Write this tag to an output stream. * * @param oOS the output stream to which this tag is to be written * @throws ID3Exception if an error occurs while writing */ abstract public void write(OutputStream oOS) throws ID3Exception; /* (non-Javadoc) * @see java.lang.Object#toString() */ public String toString() { StringBuffer sbText = new StringBuffer(); sbText.append("Unsynchronization: " + m_bUnsynchronizationUsedFlag + "\nExtended header: " + m_bExtendedHeaderFlag + "\nExperimental: " + m_bExperimentalFlag + "\nCRC: " + m_bCRCDataFlag + "\nPadding length: " + + m_iPaddingLength + "\nNum frames: " + m_oFrameIdToFrameMap.size()); Iterator oIter = m_oFrameIdToFrameMap.keySet().iterator(); while(oIter.hasNext()) { String sFrameId = (String)oIter.next(); sbText.append("\n" + ((ID3V2Frame)m_oFrameIdToFrameMap.get(sFrameId))); } return sbText.toString(); } /** Read a tag from an input stream. * * @param oIS the input stream from which to read a tag * @return the tag read * @throws ID3Exception if an error occurs while reading the tag */ public static ID3V2Tag read(InputStream oIS) throws ID3Exception { try { ID3DataInputStream oID3DIS = new ID3DataInputStream(oIS); // check which version of v2 tags we have int iMinorVersion = oID3DIS.readUnsignedByte(); int iPatchVersion = oID3DIS.readUnsignedByte(); if (iMinorVersion == 3) { // there is a tag, we must read it ID3V2Tag oID3V2Tag = ID3V2_3_0Tag.internalRead(oID3DIS); return oID3V2Tag; } else { //TODO: If we're going to support >2.3.0 tags, do that here. return null; } } catch (ID3Exception e) { throw e; } catch (Exception e) { throw new ID3Exception("Error reading tag.", e); } } /** Set the unsynchronization status. * * @param bUnsynchronizationUsed an indication of whether unsynchronization should be used when writing this tag */ public void setUnsynchronization(boolean bUnsynchronizationUsed) { m_bUnsynchronizationUsedFlag = bUnsynchronizationUsed; } /** Get the current unsynchronization status for this tag. * * @return the current status of the unsynchronization flag */ public boolean getUnsynchronization() { return m_bUnsynchronizationUsedFlag; } /** Set the extended header flag for this tag. * * @param bExtendedHeaderUsed an indication of whether the extended header should be included in this tag */ public void setExtendedHeader(boolean bExtendedHeaderUsed) { m_bExtendedHeaderFlag = bExtendedHeaderUsed; } /** Get the current extended header status for this tag. * * @return the current status of the extended header flag */ public boolean getExtendedHeader() { return m_bExtendedHeaderFlag; } /** Set the CRC flag (extended header must be enabled before this flag can be set. * * @param bCRCUsed an indication of whether a CRC value should be included in the extended header of this tag */ public void setCRC(boolean bCRCUsed) throws ID3Exception { if ( ! m_bExtendedHeaderFlag) { throw new ID3Exception("The CRC flag cannot be set unless the extended header flag is set first."); } m_bCRCDataFlag = bCRCUsed; } /** Get the current CRC status for the extended header in this frame. * * @return true if the extended header is enabled, and the CRC flag is also enabled, or false otherwise */ public boolean getCRC() { return m_bCRCDataFlag; } /** Set the default padding length to be added at the end of newly created tags. * * NOTE: When read by Winamp, it seems the last frame in a v2 tag is not seen, unless there are at least six bytes * of padding at the end of the tag. For this reason, the default padding at the end of v2 tags is set to 16. * This value can be modified if desired, but be aware of this observation regarding Winamp. * * @param iPaddingLength the padding length to use * @throws ID3Exception if the padding length value is negative */ public static void setDefaultPaddingLength(int iPaddingLength) throws ID3Exception { if (iPaddingLength < 0) { throw new ID3Exception("Padding length in ID3 V2 tag cannot be negative."); } s_iDefaultPaddingLength = iPaddingLength; } /** Set the padding length to be added at the end of this tag. * * NOTE: When read by Winamp, it seems the last frame in a v2 tag is not seen, unless there are at least six bytes * of padding at the end of the tag. For this reason, the default padding at the end of v2 tags is set to 16. * This value can be modified if desired, but be aware of this observation regarding Winamp. * * @param iPaddingLength the padding length to use * @throws ID3Exception if the padding length value is negative */ public void setPaddingLength(int iPaddingLength) throws ID3Exception { if (iPaddingLength < 0) { throw new ID3Exception("Padding length in ID3 V2 tag cannot be negative."); } m_iPaddingLength = iPaddingLength; } /** Get the default padding length currently set for newly created tags. * * @return the current padding length */ public static int getDefaultPaddingLength() { return s_iDefaultPaddingLength; } /** Get the padding length currently set for this tag. * * @return the current padding length */ public int getPaddingLength() { return m_iPaddingLength; } /** Convenience method for setting artist directly from tag. * * @param sArtist the artist name * @throws ID3Exception */ abstract public void setArtist(String sArtist) throws ID3Exception; /** Convenience method for retrieving artist directly from tag. * * @return the artist value currently set * @throws ID3Exception */ abstract public String getArtist(); /** Convenience method for setting song title directly from tag. * * @param sTitle the song title * @throws ID3Exception */ abstract public void setTitle(String sTitle) throws ID3Exception; /** Convenience method for retrieving song title directly from tag. * * @return the song title currently set * @throws ID3Exception */ abstract public String getTitle(); /** Convenience method for setting album title directly from tag. * * @param sAlbum the album title * @throws ID3Exception */ abstract public void setAlbum(String sAlbum) throws ID3Exception; /** Convenience method for retrieving album title directly from tag. * * @return the album title currently set * @throws ID3Exception */ abstract public String getAlbum(); /** Convenience method for setting year directly from tag. * * @return the year of the recording * @throws ID3Exception */ abstract public void setYear(int iYear) throws ID3Exception; /** Convenience method for retrieving year directly from tag. * * @return the year currently set * @throws ID3Exception if no year was set */ abstract public int getYear() throws ID3Exception; /** Convenience method for setting track number directly from tag. * * @param iTrackNumber the track number * @throws ID3Exception */ abstract public void setTrackNumber(int iTrackNumber) throws ID3Exception; /** Convenience method for setting track number and total number of tracks directly from tag. * * @param iTrackNumber the track number * @param iTotalTracks the total number of tracks * @throws ID3Exception */ abstract public void setTrackNumber(int iTrackNumber, int iTotalTracks) throws ID3Exception; /** Convenience method for retrieving track number directly from tag. * * @return the track number currently set * @throws ID3Exception if not track number was set */ abstract public int getTrackNumber() throws ID3Exception; /** Convenience method for retrieving total number of tracks directly from tag. * * @return the total number of tracks currently set * @throws ID3Exception if total number of tracks was not set */ abstract public int getTotalTracks() throws ID3Exception; /** Convenience method for setting genre directly from tag. * * @param sGenre the genre (free-form) * @throws ID3Exception */ abstract public void setGenre(String sGenre) throws ID3Exception; /** Convenience method for retrieving the genre directly from tag. * * @return the genre currently set * @throws ID3Exception */ abstract public String getGenre(); /** Convenience method for setting comment directly from tag. * * @param sComment the comment * @throws ID3Exception */ abstract public void setComment(String sComment) throws ID3Exception; /** Convenience method for retrieving the comment directly from tag. * * @return the comment currently set * @throws ID3Exception */ abstract public String getComment(); }