/* * ETCOID3V2Frame.java * * Created on Jan 31, 2004 * * Copyright (C)2004,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: ETCOID3V2Frame.java,v 1.10 2005/02/06 18:11:15 paul Exp $ */ package org.blinkenlights.jid3.v2; import java.io.*; import java.util.*; import org.blinkenlights.jid3.*; import org.blinkenlights.jid3.io.*; import org.blinkenlights.jid3.util.*; /** * @author paul * * Frame containing event timing codes. */ public class ETCOID3V2Frame extends ID3V2Frame { private TimestampFormat m_oTimestampFormat; private SortedMap m_oTimeToEventMap = null; /** Constructor. * * @param oTimestampFormat the format for timestamps, whether by millisecond or frame count * @throws ID3Exception if oTimestampFormat object is null */ public ETCOID3V2Frame(TimestampFormat oTimestampFormat) throws ID3Exception { if (oTimestampFormat == null) { throw new ID3Exception("Timestamp format required in ETCO frame."); } m_oTimestampFormat = oTimestampFormat; m_oTimeToEventMap = new TreeMap(); } public ETCOID3V2Frame(InputStream oIS) throws ID3Exception { // Parse out the text encoding and text string from the raw data try { ID3DataInputStream oFrameDataID3DIS = new ID3DataInputStream(oIS); // timestamp format m_oTimestampFormat = new TimestampFormat((byte)oFrameDataID3DIS.readUnsignedByte()); // read events and timestamps to end m_oTimeToEventMap = new TreeMap(); while (oFrameDataID3DIS.available() > 0) { byte byTypeOfEvent = (byte)oFrameDataID3DIS.readUnsignedByte(); EventType oEventType = new EventType(byTypeOfEvent); int iTimestamp = oFrameDataID3DIS.readBE32(); addEvent(new Event(oEventType, iTimestamp)); } } catch (Exception e) { throw new InvalidFrameID3Exception(e); } } public void accept(ID3Visitor oID3Visitor) { oID3Visitor.visitETCOID3V2Frame(this); } /** Get the set timestamp format. * * @return the currently set timestamp format */ public TimestampFormat getTimestampFormat() { return m_oTimestampFormat; } /** Add an event to the list. Note, only one event per exact time can be defined. An event set at * a time for which another event already is set will overwrite the existing one. * * @param oEvent the event being set */ public void addEvent(Event oEvent) { m_oTimeToEventMap.put(new Integer(oEvent.getTimestamp()), oEvent); } /** Get the event which has been set for a given time. * * @return the event set for the given time, or null if no event has been set for that time * @throws ID3Exception if the timestamp specified is negative */ public Event getEvent(int iTimestamp) throws ID3Exception { if (iTimestamp < 0) { throw new ID3Exception("Negative timestamps are not valid in ETCO frames."); } return (Event)m_oTimeToEventMap.get(new Integer(iTimestamp)); } /** Get all events which have been set. Events are returned in sorted order by timestamp. * * @return an array of Events which have been set */ public Event[] getEvents() { return (Event[])m_oTimeToEventMap.values().toArray(new Event[0]); } /** Remove the event set for a specific time. * * @return the event which was previously set for the given time, or null if no even was set for that time */ public Event removeEvent(Event oEvent) { return (Event)m_oTimeToEventMap.remove(new Integer(oEvent.getTimestamp())); } protected byte[] getFrameId() { return "ETCO".getBytes(); } public String toString() { StringBuffer sbOutput = new StringBuffer(); sbOutput.append("Event Timing Codes: Timestamp format = " + m_oTimestampFormat.getValue()); sbOutput.append(", Events = "); Iterator oIter = m_oTimeToEventMap.values().iterator(); while (oIter.hasNext()) { Event oEvent = (Event)oIter.next(); sbOutput.append(oEvent.getEventType().getValue() + ":" + oEvent.getTimestamp() + " "); } return sbOutput.toString(); } protected void writeBody(ID3DataOutputStream oIDOS) throws IOException { // timestamp format oIDOS.writeUnsignedByte(m_oTimestampFormat.getValue()); // events Iterator oIter = m_oTimeToEventMap.values().iterator(); while (oIter.hasNext()) { Event oEvent = (Event)oIter.next(); oIDOS.writeUnsignedByte(oEvent.getEventType().getValue()); oIDOS.writeBE32(oEvent.getTimestamp()); } } /** Timestamp format. The timestamp format is used to specify how offsets from the start of the * the file are measured for events. */ public static class TimestampFormat { private byte m_byTimestampFormat; private TimestampFormat(byte byTimestampFormat) { m_byTimestampFormat = byTimestampFormat; } private byte getValue() { return m_byTimestampFormat; } /** Timestamp format indicating that timestamps are measured in MPEG frames from the start of the file. */ public static final TimestampFormat ABSOLUTE_MPEG_FRAMES = new TimestampFormat((byte)0x01); /** Timestamp format indicating that timestamps are measured in milliseconds from the start of the file. */ public static final TimestampFormat ABSOLUTE_MILLISECONDS = new TimestampFormat((byte)0x02); public boolean equals(Object oOther) { try { TimestampFormat oOtherTF = (TimestampFormat)oOther; return (m_byTimestampFormat == oOtherTF.m_byTimestampFormat); } catch (Exception e) { return false; } } } /** Event types. */ public static class EventType { private byte m_byTypeOfEvent; private EventType(byte byTypeOfEvent) { m_byTypeOfEvent = byTypeOfEvent; } private byte getValue() { return m_byTypeOfEvent; } /** Pre-defined event types. */ public static final EventType PADDING = new EventType((byte)0x00); public static final EventType END_OF_INITIAL_SILENCE = new EventType((byte)0x01); public static final EventType INTRO_START = new EventType((byte)0x02); public static final EventType MAINPART_START = new EventType((byte)0x03); public static final EventType OUTRO_START = new EventType((byte)0x04); public static final EventType OUTRO_END = new EventType((byte)0x05); public static final EventType VERSE_START = new EventType((byte)0x06); public static final EventType REFRAIN_START = new EventType((byte)0x07); public static final EventType INTERLUDE_START = new EventType((byte)0x08); public static final EventType THEME_START = new EventType((byte)0x09); public static final EventType VARIATION_START = new EventType((byte)0x0a); public static final EventType KEY_CHANGE = new EventType((byte)0x0b); public static final EventType TIME_CHANGE = new EventType((byte)0x0c); public static final EventType MOMENTARY_UNWANTED_NOISE = new EventType((byte)0x0d); public static final EventType SUSTAINED_NOISE = new EventType((byte)0x0e); public static final EventType SUSTAINED_NOISE_END = new EventType((byte)0x0f); public static final EventType INTRO_END = new EventType((byte)0x10); public static final EventType MAINPART_END = new EventType((byte)0x11); public static final EventType VERSE_END = new EventType((byte)0x12); public static final EventType REFRAIN_END = new EventType((byte)0x13); public static final EventType THEME_END = new EventType((byte)0x14); public static final EventType USER_DEFINED_01 = new EventType((byte)0xe0); public static final EventType USER_DEFINED_02 = new EventType((byte)0xe1); public static final EventType USER_DEFINED_03 = new EventType((byte)0xe2); public static final EventType USER_DEFINED_04 = new EventType((byte)0xe3); public static final EventType USER_DEFINED_05 = new EventType((byte)0xe4); public static final EventType USER_DEFINED_06 = new EventType((byte)0xe5); public static final EventType USER_DEFINED_07 = new EventType((byte)0xe6); public static final EventType USER_DEFINED_08 = new EventType((byte)0xe7); public static final EventType USER_DEFINED_09 = new EventType((byte)0xe8); public static final EventType USER_DEFINED_10 = new EventType((byte)0xe9); public static final EventType USER_DEFINED_11 = new EventType((byte)0xea); public static final EventType USER_DEFINED_12 = new EventType((byte)0xeb); public static final EventType USER_DEFINED_13 = new EventType((byte)0xec); public static final EventType USER_DEFINED_14 = new EventType((byte)0xed); public static final EventType USER_DEFINED_15 = new EventType((byte)0xee); public static final EventType USER_DEFINED_16 = new EventType((byte)0xef); public static final EventType AUDIO_END = new EventType((byte)0xfd); public static final EventType AUDIO_FILE_ENDS = new EventType((byte)0xfe); //NOTE: $FF seems to be defined in the spec, but I do not understand the definition... public boolean equals(Object oOther) { try { EventType oOtherET = (EventType)oOther; return (m_byTypeOfEvent == oOtherET.m_byTypeOfEvent); } catch (Exception e) { return false; } } } /** Event. Events are comprised of a timestamp, and a type of event indicated for that time. */ public static class Event { private EventType m_oEventType; private int m_iTimestamp; /** Constructor. * * @param oEventType the type of event to be indicated * @param iTimestamp the time of the event, specified in the chosen format * @throws ID3Exception if the timestamp value is negative */ public Event(EventType oEventType, int iTimestamp) throws ID3Exception { if (iTimestamp < 0) { throw new ID3Exception("Negative timestamps are not valid in ETCO frames."); } m_oEventType = oEventType; m_iTimestamp = iTimestamp; } /** Get the type of event specified. * * @return the type of event specified */ public EventType getEventType() { return m_oEventType; } /** Get the timestamp for this event. * * @return the timestamp for this event, to be interpreted in the current chosen format */ public int getTimestamp() { return m_iTimestamp; } public boolean equals(Object oOther) { try { Event oOtherEvent = (Event)oOther; return ((m_iTimestamp == oOtherEvent.m_iTimestamp) && m_oEventType.equals(oOtherEvent.m_oEventType)); } catch (Exception e) { return false; } } } public boolean equals(Object oOther) { if ((oOther == null) || (!(oOther instanceof ETCOID3V2Frame))) { return false; } ETCOID3V2Frame oOtherETCO = (ETCOID3V2Frame)oOther; return (m_oTimestampFormat.equals(oOtherETCO.m_oTimestampFormat) && m_oTimeToEventMap.equals(oOtherETCO.m_oTimeToEventMap)); } }