/*
* 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));
}
}