package net.sf.fmj.utility;
import java.awt.Dimension;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.media.Format;
import javax.media.format.AudioFormat;
import javax.media.format.H261Format;
import javax.media.format.H263Format;
import javax.media.format.IndexedColorFormat;
import javax.media.format.JPEGFormat;
import javax.media.format.RGBFormat;
import javax.media.format.VideoFormat;
import javax.media.format.YUVFormat;
import net.sf.fmj.media.BonusAudioFormatEncodings;
import net.sf.fmj.media.BonusVideoFormatEncodings;
import net.sf.fmj.media.format.GIFFormat;
import net.sf.fmj.media.format.PNGFormat;
/**
* A class for converting Format objects to and from strings that can be used as arguments
* in command-line programs, or as parameters in URLs.
*
* The syntax is this: all elements are separated by a colon.
* Everything is uppercase by default, but case is ignored. Only thing that is lowercase is x in dimension.
* Generally, each item corresponds to a constructor argument. The Format subclass is inferred from the encoding.
* ? is used to indicate Format.NOT_SPECIFIED (-1).
* floating point values in audio formats are done as integers.
* In audio formats, Big endian is B, little endian is L, signed is S, unsigned is U
* Data types: B is byte[], S is short[], I is int[]
* Dimension: [width]x[height], like "640x480"
* Trailing not specified values may be omitted.
*
* new AudioFormat(AudioFormat.LINEAR, 44100.0, 16, 2)
* would be
* LINEAR:44100:16:2
*
*
*
* TODO: support WavAudioFormat, video formats, and other missing audio formats.
*
* @author Ken Larson
*
*/
public class FormatArgUtils
{
private static final char SEP = ':';
public static final String BYTE_ARRAY = "B";
public static final String SHORT_ARRAY = "S";
public static final String INT_ARRAY = "I";
public static final String NOT_SPECIFIED = "?";
// audio format constants:
public static final String BIG_ENDIAN = "B";
public static final String LITTLE_ENDIAN = "L";
public static final String SIGNED = "S";
public static final String UNSIGNED = "U";
private static final Map<String, String> formatEncodings = new HashMap<String, String>(); // corect case
private static final Map<String, Class> formatClasses = new HashMap<String, Class>();
static
{
buildFormatMap();
}
private static final void buildFormatMap()
{
addAudioFormat(AudioFormat.LINEAR);
addAudioFormat(AudioFormat.ULAW); // = "ULAW";
addAudioFormat(AudioFormat.ULAW_RTP); // = "ULAW/rtp";
addAudioFormat(AudioFormat.ALAW); // = "alaw"; // strange that this is lower case and ULAW is not...
addAudioFormat(AudioFormat.IMA4); // = "ima4";
addAudioFormat(AudioFormat.IMA4_MS); // = "ima4/ms";
addAudioFormat(AudioFormat.MSADPCM); // = "msadpcm";
addAudioFormat(AudioFormat.DVI); // = "dvi";
addAudioFormat(AudioFormat.DVI_RTP); // = "dvi/rtp";
addAudioFormat(AudioFormat.G723); // = "g723";
addAudioFormat(AudioFormat.G723_RTP); // = "g723/rtp";
addAudioFormat(AudioFormat.G728); // = "g728";
addAudioFormat(AudioFormat.G728_RTP); // = "g728/rtp";
addAudioFormat(AudioFormat.G729); // = "g729";
addAudioFormat(AudioFormat.G729_RTP); // = "g729/rtp";
addAudioFormat(AudioFormat.G729A); // = "g729a";
addAudioFormat(AudioFormat.G729A_RTP); // = "g729a/rtp";
addAudioFormat(AudioFormat.GSM); // = "gsm";
addAudioFormat(AudioFormat.GSM_MS); // = "gsm/ms";
addAudioFormat(AudioFormat.GSM_RTP); // = "gsm/rtp";
addAudioFormat(AudioFormat.MAC3); // = "MAC3";
addAudioFormat(AudioFormat.MAC6); // = "MAC6";
addAudioFormat(AudioFormat.TRUESPEECH); // = "truespeech";
addAudioFormat(AudioFormat.MSNAUDIO); // = "msnaudio";
addAudioFormat(AudioFormat.MPEGLAYER3); // = "mpeglayer3";
addAudioFormat(AudioFormat.VOXWAREAC8); // = "voxwareac8";
addAudioFormat(AudioFormat.VOXWAREAC10); // = "voxwareac10";
addAudioFormat(AudioFormat.VOXWAREAC16); // = "voxwareac16";
addAudioFormat(AudioFormat.VOXWAREAC20); // = "voxwareac20";
addAudioFormat(AudioFormat.VOXWAREMETAVOICE); // = "voxwaremetavoice";
addAudioFormat(AudioFormat.VOXWAREMETASOUND); // = "voxwaremetasound";
addAudioFormat(AudioFormat.VOXWARERT29H); // = "voxwarert29h";
addAudioFormat(AudioFormat.VOXWAREVR12); // = "voxwarevr12";
addAudioFormat(AudioFormat.VOXWAREVR18); // = "voxwarevr18";
addAudioFormat(AudioFormat.VOXWARETQ40); // = "voxwaretq40";
addAudioFormat(AudioFormat.VOXWARETQ60); // = "voxwaretq60";
addAudioFormat(AudioFormat.MSRT24); // = "msrt24";
addAudioFormat(AudioFormat.MPEG); // = "mpegaudio";
addAudioFormat(AudioFormat.MPEG_RTP); // = "mpegaudio/rtp";
addAudioFormat(AudioFormat.DOLBYAC3); // = "dolbyac3";
for (String e : BonusAudioFormatEncodings.ALL)
addAudioFormat(e);
// TODO: MpegEncoding using
// MpegEncoding.MPEG1L1,
// MpegEncoding.MPEG1L2,
// MpegEncoding.MPEG1L3,
// MpegEncoding.MPEG2DOT5L1,
// MpegEncoding.MPEG2DOT5L2,
// MpegEncoding.MPEG2DOT5L3,
// MpegEncoding.MPEG2L1,
// MpegEncoding.MPEG2L2,
// MpegEncoding.MPEG2L3,
// TODO: VorbisEncoding using VorbisEncoding.VORBISENC
// Video formats:
addVideoFormat(VideoFormat.CINEPAK); // ="cvid";
addFormat(VideoFormat.JPEG, JPEGFormat.class);
addVideoFormat(VideoFormat.JPEG_RTP); // ="jpeg/rtp";
addVideoFormat(VideoFormat.MPEG); // ="mpeg";
addVideoFormat(VideoFormat.MPEG_RTP); // ="mpeg/rtp";
addFormat(VideoFormat.H261, H261Format.class);
addVideoFormat(VideoFormat.H261_RTP); // ="h261/rtp";
addFormat(VideoFormat.H263, H263Format.class);
addVideoFormat(VideoFormat.H263_RTP); // ="h263/rtp";
addVideoFormat(VideoFormat.H263_1998_RTP); // ="h263-1998/rtp";
addFormat(VideoFormat.RGB, RGBFormat.class);
addFormat(VideoFormat.YUV, YUVFormat.class);
addFormat(VideoFormat.IRGB, IndexedColorFormat.class);
addVideoFormat(VideoFormat.SMC); // ="smc";
addVideoFormat(VideoFormat.RLE); // ="rle";
addVideoFormat(VideoFormat.RPZA); // ="rpza";
addVideoFormat(VideoFormat.MJPG); // ="mjpg";
addVideoFormat(VideoFormat.MJPEGA); // ="mjpa";
addVideoFormat(VideoFormat.MJPEGB); // ="mjpb";
addVideoFormat(VideoFormat.INDEO32); // ="iv32";
addVideoFormat(VideoFormat.INDEO41); // ="iv41";
addVideoFormat(VideoFormat.INDEO50); // ="iv50";
// TODO: AviVideoFormat
addFormat(BonusVideoFormatEncodings.GIF, GIFFormat.class);
addFormat(BonusVideoFormatEncodings.PNG, PNGFormat.class);
}
public static String toString(Format f)
{
final List<String> list = new ArrayList<String>();
list.add(f.getEncoding().toUpperCase());
if (f instanceof AudioFormat)
{
final AudioFormat af = (AudioFormat) f;
list.add(intToStr((int) af.getSampleRate()));
list.add(intToStr(af.getSampleSizeInBits()));
list.add(intToStr(af.getChannels()));
list.add(endianToStr(af.getEndian()));
list.add(signedToStr(af.getSigned()));
list.add(intToStr(af.getFrameSizeInBits()));
list.add(intToStr((int) af.getFrameRate()));
if (af.getDataType() != null && af.getDataType() != Format.byteArray)
list.add(dataTypeToStr(af.getDataType()));
}
else if (f instanceof VideoFormat)
{
final VideoFormat vf = (VideoFormat) f;
if (f.getClass() == JPEGFormat.class)
{
final JPEGFormat jf = (JPEGFormat) vf;
list.add(dimensionToStr(jf.getSize()));
list.add(intToStr(jf.getMaxDataLength()));
if (jf.getDataType() != null && jf.getDataType() != Format.byteArray)
list.add(dataTypeToStr(jf.getDataType()));
list.add(floatToStr(jf.getFrameRate()));
// TODO: Q, decimation
}
else if (f.getClass() == GIFFormat.class)
{
final GIFFormat gf = (GIFFormat) vf;
list.add(dimensionToStr(gf.getSize()));
list.add(intToStr(gf.getMaxDataLength()));
if (gf.getDataType() != null && gf.getDataType() != Format.byteArray)
list.add(dataTypeToStr(gf.getDataType()));
list.add(floatToStr(gf.getFrameRate()));
}
else if (f.getClass() == PNGFormat.class)
{
final PNGFormat pf = (PNGFormat) vf;
list.add(dimensionToStr(pf.getSize()));
list.add(intToStr(pf.getMaxDataLength()));
if (pf.getDataType() != null && pf.getDataType() != Format.byteArray)
list.add(dataTypeToStr(pf.getDataType()));
list.add(floatToStr(pf.getFrameRate()));
}
else if (f.getClass() == VideoFormat.class)
{
list.add(dimensionToStr(vf.getSize()));
list.add(intToStr(vf.getMaxDataLength()));
if (vf.getDataType() != null && vf.getDataType() != Format.byteArray)
list.add(dataTypeToStr(vf.getDataType()));
list.add(floatToStr(vf.getFrameRate()));
}
else if (f.getClass() == RGBFormat.class)
{
final RGBFormat rf = (RGBFormat) vf;
list.add(dimensionToStr(vf.getSize()));
list.add(intToStr(vf.getMaxDataLength()));
if (vf.getDataType() != null && vf.getDataType() != Format.byteArray)
list.add(dataTypeToStr(vf.getDataType()));
list.add(floatToStr(vf.getFrameRate()));
list.add(intToStr(rf.getBitsPerPixel()));
list.add(intToStr(rf.getRedMask())); // TODO: hex?
list.add(intToStr(rf.getGreenMask()));
list.add(intToStr(rf.getBlueMask()));
list.add(intToStr(rf.getPixelStride()));
list.add(intToStr(rf.getLineStride()));
list.add(intToStr(rf.getFlipped())); // TODO: use a string code for this?
list.add(rgbFormatEndianToStr(rf.getEndian()));
}
else
throw new IllegalArgumentException("Unknown or unsupported format: " + f);
}
else
{ throw new IllegalArgumentException("" + f);
}
// remove any default values from the end.
while (list.get(list.size() - 1) == null || list.get(list.size() - 1).equals(NOT_SPECIFIED))
list.remove(list.size() - 1);
final StringBuilder b = new StringBuilder();
for (int i = 0; i < list.size(); ++i)
{ if (i > 0)
b.append(SEP);
b.append(list.get(i));
}
return b.toString();
}
private static final String intToStr(int i)
{
if (i == Format.NOT_SPECIFIED)
return NOT_SPECIFIED;
else
return "" + i;
}
private static final String floatToStr(float v)
{
if (v == Format.NOT_SPECIFIED)
return NOT_SPECIFIED;
else
return "" + v;
}
private static final String dimensionToStr(Dimension d)
{
if (d == null)
return NOT_SPECIFIED;
return ((int) d.getWidth()) + "x" + ((int) d.getHeight());
}
private static final String dataTypeToStr(Class clazz)
{
if (clazz == null)
{ return NOT_SPECIFIED;
}
if (clazz == Format.byteArray)
{ return BYTE_ARRAY;
}
if (clazz == Format.shortArray)
{ return SHORT_ARRAY;
}
if (clazz == Format.intArray)
{ return INT_ARRAY;
}
throw new IllegalArgumentException("" + clazz);
}
private static final String endianToStr(int endian)
{
if (endian == Format.NOT_SPECIFIED)
return NOT_SPECIFIED;
else if (endian == AudioFormat.BIG_ENDIAN)
return BIG_ENDIAN;
else if (endian == AudioFormat.LITTLE_ENDIAN)
return LITTLE_ENDIAN;
else
throw new IllegalArgumentException("Unknown endianness: " + endian);
}
private static final String rgbFormatEndianToStr(int endian)
{
if (endian == Format.NOT_SPECIFIED)
return NOT_SPECIFIED;
else if (endian == RGBFormat.BIG_ENDIAN)
return BIG_ENDIAN;
else if (endian == RGBFormat.LITTLE_ENDIAN)
return LITTLE_ENDIAN;
else
throw new IllegalArgumentException("Unknown endianness: " + endian);
}
private static final String signedToStr(int signed)
{
if (signed == Format.NOT_SPECIFIED)
return NOT_SPECIFIED;
else if (signed == AudioFormat.SIGNED)
return SIGNED;
else if (signed == AudioFormat.UNSIGNED)
return UNSIGNED;
else
throw new IllegalArgumentException("Unknown signedness: " + signed);
}
private static final void addAudioFormat(String s)
{
addFormat(s, AudioFormat.class);
}
private static final void addVideoFormat(String s)
{
addFormat(s, VideoFormat.class);
}
private static final void addFormat(String s, Class clazz)
{
formatClasses.put(s.toLowerCase(), clazz);
formatEncodings.put(s.toLowerCase(), s);
}
private static class Tokens
{
private final String[] items;
private int ix;
public Tokens(String[] items)
{
super();
this.items = items;
ix = 0;
}
public String nextString()
{
return nextString(null);
}
public String nextString(String defaultResult)
{
if (ix >= items.length)
return defaultResult;
final String result = items[ix];
++ix;
return result;
}
public double nextDouble() throws ParseException
{
final String s = nextString();
if (s == null)
return Format.NOT_SPECIFIED;
if (s.equals(NOT_SPECIFIED))
return Format.NOT_SPECIFIED;
try
{
return Double.parseDouble(s);
}
catch (NumberFormatException e)
{
throw new ParseException("Expected double: " + s, -1);
}
}
public float nextFloat() throws ParseException
{
final String s = nextString();
if (s == null)
return Format.NOT_SPECIFIED;
if (s.equals(NOT_SPECIFIED))
return Format.NOT_SPECIFIED;
try
{
return Float.parseFloat(s);
}
catch (NumberFormatException e)
{
throw new ParseException("Expected float: " + s, -1);
}
}
public int nextInt() throws ParseException
{
final String s = nextString();
if (s == null)
return Format.NOT_SPECIFIED;
if (s.equals(NOT_SPECIFIED))
return Format.NOT_SPECIFIED;
try
{
return Integer.parseInt(s);
}
catch (NumberFormatException e)
{
throw new ParseException("Expected integer: " + s, -1);
}
}
public int nextSigned() throws ParseException
{
String s = nextString();
if (s == null)
return Format.NOT_SPECIFIED;
if (s.equals(NOT_SPECIFIED))
return Format.NOT_SPECIFIED;
s = s.toUpperCase();
if (s.equals(UNSIGNED))
return AudioFormat.UNSIGNED;
else if (s.equals(SIGNED))
return AudioFormat.SIGNED;
else
throw new ParseException("Expected one of [" + UNSIGNED + "," + UNSIGNED + "]: " + s, -1);
}
public int nextEndian() throws ParseException
{
String s = nextString();
if (s == null)
return Format.NOT_SPECIFIED;
if (s.equals(NOT_SPECIFIED))
return Format.NOT_SPECIFIED;
s = s.toUpperCase();
if (s.equals(BIG_ENDIAN))
return AudioFormat.BIG_ENDIAN;
else if (s.equals(LITTLE_ENDIAN))
return AudioFormat.LITTLE_ENDIAN;
else
throw new ParseException("Expected one of [" + BIG_ENDIAN + "," + LITTLE_ENDIAN + "]: " + s, -1);
}
public int nextRGBFormatEndian() throws ParseException
{
String s = nextString();
if (s == null)
return Format.NOT_SPECIFIED;
if (s.equals(NOT_SPECIFIED))
return Format.NOT_SPECIFIED;
s = s.toUpperCase();
if (s.equals(BIG_ENDIAN))
return RGBFormat.BIG_ENDIAN;
else if (s.equals(LITTLE_ENDIAN))
return RGBFormat.LITTLE_ENDIAN;
else
throw new ParseException("Expected one of [" + BIG_ENDIAN + "," + LITTLE_ENDIAN + "]: " + s, -1);
}
public Class nextDataType() throws ParseException
{
String s = nextString();
if (s == null)
return null;
if (s.equals(NOT_SPECIFIED))
return null;
s = s.toUpperCase();
if (s.equals(BYTE_ARRAY))
return Format.byteArray;
else if (s.equals(SHORT_ARRAY))
return Format.shortArray;
else if (s.equals(INT_ARRAY))
return Format.intArray;
else
throw new ParseException("Expected one of [" + BYTE_ARRAY + "," + SHORT_ARRAY + "," + INT_ARRAY + "]: " + s, -1);
}
public Dimension nextDimension() throws ParseException
{
String s = nextString();
if (s == null)
return null;
if (s.equals(NOT_SPECIFIED))
return null;
s = s.toUpperCase();
String[] strings = s.split("X");
if (strings.length != 2)
throw new ParseException("Expected WIDTHxHEIGHT: " + s, -1);
int width;
int height;
try
{
width = Integer.parseInt(strings[0]);
}
catch (NumberFormatException e)
{ throw new ParseException("Expected integer: " + strings[0], -1);
}
try
{
height = Integer.parseInt(strings[1]);
}
catch (NumberFormatException e)
{ throw new ParseException("Expected integer: " + strings[1], -1);
}
return new Dimension(width, height);
}
}
public static Format parse(String s) throws ParseException
{
final String[] strings = s.split("" + SEP);
final Tokens t = new Tokens(strings);
int ix = 0;
final String encodingIgnoreCase = t.nextString(null);
if (encodingIgnoreCase == null)
throw new ParseException("No encoding specified", 0);
final Class formatClass = formatClasses.get(encodingIgnoreCase.toLowerCase());
if (formatClass == null)
throw new ParseException("Unknown encoding: " + encodingIgnoreCase, -1);
final String encoding = formatEncodings.get(encodingIgnoreCase.toLowerCase());
if (encoding == null)
throw new ParseException("Unknown encoding: " + encodingIgnoreCase, -1);
if (AudioFormat.class.isAssignableFrom(formatClass))
{
final double sampleRate = t.nextDouble();
final int sampleSizeInBits = t.nextInt();
final int channels = t.nextInt();
final int endian = t.nextEndian();
final int signed = t.nextSigned();
final int frameSizeInBits = t.nextInt();
final double frameRate = t.nextDouble();
Class dataType = t.nextDataType();
if (dataType == null)
dataType = Format.byteArray; // default
return new AudioFormat(encoding, sampleRate,
sampleSizeInBits, channels, endian, signed,
frameSizeInBits, frameRate, dataType);
}
else if (VideoFormat.class.isAssignableFrom(formatClass))
{
if (formatClass == JPEGFormat.class)
{
final java.awt.Dimension size = t.nextDimension();
final int maxDataLength = t.nextInt();
Class dataType = t.nextDataType();
if (dataType == null)
dataType = Format.byteArray; // default
final float frameRate = t.nextFloat();
final int q = Format.NOT_SPECIFIED; // TODO
final int dec = Format.NOT_SPECIFIED; // TODO
return new JPEGFormat(size, maxDataLength, dataType, frameRate, q, dec);
}
else if (formatClass == GIFFormat.class)
{
final java.awt.Dimension size = t.nextDimension();
final int maxDataLength = t.nextInt();
Class dataType = t.nextDataType();
if (dataType == null)
dataType = Format.byteArray; // default
final float frameRate = t.nextFloat();
return new GIFFormat(size, maxDataLength, dataType, frameRate);
}
else if (formatClass == PNGFormat.class)
{
final java.awt.Dimension size = t.nextDimension();
final int maxDataLength = t.nextInt();
Class dataType = t.nextDataType();
if (dataType == null)
dataType = Format.byteArray; // default
final float frameRate = t.nextFloat();
return new PNGFormat(size, maxDataLength, dataType, frameRate);
}
else if (formatClass == VideoFormat.class)
{
final java.awt.Dimension size = t.nextDimension();
final int maxDataLength = t.nextInt();
Class dataType = t.nextDataType();
if (dataType == null)
dataType = Format.byteArray; // default
final float frameRate = t.nextFloat();
return new VideoFormat(encoding, size, maxDataLength, dataType, frameRate);
}
else if (formatClass == RGBFormat.class)
{
final java.awt.Dimension size = t.nextDimension();
final int maxDataLength = t.nextInt();
Class dataType = t.nextDataType();
if (dataType == null)
dataType = Format.byteArray; // default
final float frameRate = t.nextFloat();
final int bitsPerPixel = t.nextInt();
final int red = t.nextInt();
final int green = t.nextInt();
final int blue = t.nextInt();
final int pixelStride = t.nextInt();
final int lineStride = t.nextInt();
final int flipped = t.nextInt();
final int endian = t.nextRGBFormatEndian();
if (pixelStride == -1 && lineStride == -1 && flipped == -1 && endian == -1)
return new RGBFormat(size, maxDataLength, dataType, frameRate, bitsPerPixel, red, green, blue);
return new RGBFormat(size, maxDataLength, dataType, frameRate, bitsPerPixel, red, green, blue, pixelStride, lineStride, flipped, endian);
}
// public RGBFormat(java.awt.Dimension size, int maxDataLength,
// Class dataType, float frameRate, int bitsPerPixel,
// int red, int green, int blue, int pixelStride, int lineStride,
// int flipped, int endian)
// TODO: others
throw new RuntimeException("TODO: Unknown class: " + formatClass);
}
else
{
throw new RuntimeException("Unknown class: " + formatClass);
}
}
}