001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029 package org.hd.d.pg2k.svrCore.mediahandler;
030
031 import java.awt.Dimension;
032 import java.io.DataInputStream;
033 import java.io.FilterInputStream;
034 import java.io.IOException;
035 import java.io.InputStream;
036 import java.util.zip.InflaterInputStream;
037
038 import javax.xml.parsers.DocumentBuilder;
039 import javax.xml.parsers.DocumentBuilderFactory;
040 import javax.xml.parsers.ParserConfigurationException;
041
042 import org.hd.d.pg2k.svrCore.Name.ExhibitFull;
043 import org.hd.d.pg2k.svrCore.MIME.AbstractHandler;
044 import org.hd.d.pg2k.svrCore.MIME.ExhibitMIME;
045 import org.w3c.dom.Document;
046 import org.w3c.dom.Element;
047 import org.w3c.dom.Node;
048
049 /**Basic handler for SWF file (MacroMedia Shockwave Flash).
050 */
051 public final class swf extends AbstractHandler
052 {
053 /**Get internal type of ExhibitMIME type; never null. */
054 public ExhibitMIME.ExhibitTypeParameters getExhibitType()
055 { return(ExhibitMIME.getParamsByType(ExhibitMIME.ET_SWF)); }
056
057
058 /**TWIPS to pixels scaling factor assuming 72dpi screen resolution. */
059 private static final int TWIPS_TO_PIXELS = 20;
060
061 /**Efficiently get X,Y pixel dimensions of SWF exhibit, else null if dimensions cannot be computed.
062 * This <strong>does not close its input stream</strong> when done.
063 * <p>
064 * This should work efficiently, by directly reading (opening) bytes
065 * of the exhibit passed as an input stream.
066 * <p>
067 * This will only work correctly if the exhibit is of the correct type,
068 * eg its magic number must already have been tested.
069 *
070 * @param is the exhibit as a binary data stream
071 * @throws java.io.IOException in case of problems with corrupt data
072 * (or a broken exhibit)
073 */
074 @Override public Dimension get2DImageDimensions(final InputStream is)
075 throws IOException
076 {
077 // Don't allow the underlying stream to be closed.
078 final InputStream isWrapped = new FilterInputStream(is){
079 /**Ignore any attempt to close the stream. */
080 @Override public void close() { }
081 };
082 final SWFMetaData swfMetaData = new SWFMetaData(isWrapped);
083 final Dimension result = new Dimension(
084 swfMetaData.xmax / TWIPS_TO_PIXELS,
085 swfMetaData.ymax / TWIPS_TO_PIXELS);
086 return(result);
087 }
088
089 /**Immutable meta-data for SWF exhibit. */
090 public static final class SWFMetaData
091 {
092 /**SWF version. */
093 public final int version;
094 public final long length;
095 /**True if file in compressed format. */
096 public final boolean compressed;
097 public final int xmin;
098 public final int xmax;
099 public final int ymin;
100 public final int ymax;
101 /**Frame rate. */
102 public final float rate;
103 /**Frame count. */
104 public final int count;
105
106 /**Parse meta-data from an input stream. */
107 public SWFMetaData(final InputStream is)
108 throws IOException
109 {
110 InflaterInputStream inflaterInputStream = null;
111 try
112 {
113 // Starts with CWS for compressed format, FWS for uncompressed.
114 DataInputStream dis = new DataInputStream(is);
115 final int firstChar = dis.readUnsignedByte();
116 dis.skip(2); // Skip the "WS"...
117
118 version = dis.readUnsignedByte();
119 length = 0x0ffffffffL & dis.readInt();
120
121 compressed = (version >= 6) && ('C' == firstChar);
122 if(compressed)
123 {
124 // Start reading from the decompressed stream instead.
125 inflaterInputStream = new InflaterInputStream(dis);
126 dis = new DataInputStream(inflaterInputStream);
127 }
128
129 long sizeBits = dis.readLong();
130 final int bitsPerLenValue = (int) (sizeBits >>> 59);
131 sizeBits <<= 5;
132
133 xmin = (int) (sizeBits >> (64 - bitsPerLenValue));
134 sizeBits <<= bitsPerLenValue;
135 xmax = (int) (sizeBits >> (64 - bitsPerLenValue));
136 sizeBits <<= bitsPerLenValue;
137 ymin = (int) (sizeBits >> (64 - bitsPerLenValue));
138 sizeBits <<= bitsPerLenValue;
139 ymax = (int) (sizeBits >> (64 - bitsPerLenValue));
140 sizeBits <<= bitsPerLenValue;
141
142 rate = dis.readUnsignedShort() / 256f;
143 count = dis.readUnsignedShort();
144 }
145 finally
146 {
147 // Ensure that we free any ZIP resources.
148 if(inflaterInputStream != null)
149 { inflaterInputStream.close(); }
150 }
151 }
152 }
153
154 /**Gets all available exhibit metadata as a single XML DOM tree; null if none.
155 * We do not (yet) have a schema for this and may never do so,
156 * since its structure and content will depend on various external sources
157 * and parts of (for example) the ImageIO and JAI subsystems.
158 * <p>
159 * This implementation synthesises a metadata tree from the AudioFileFormat data,
160 * if any.
161 *
162 * @param is input stream; never null
163 * @return top-level node "metadata" with captured metadata beneath, else null
164 */
165 @Override
166 public Node getMetadata(final InputStream is, final ExhibitFull exhibitName)
167 {
168 // Result: will be set non-null if we encounter any usable data.
169 Node result = null;
170
171 try
172 {
173 final SWFMetaData md = new SWFMetaData(is);
174
175 final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
176 final DocumentBuilder db = dbf.newDocumentBuilder();
177 final Document doc = db.newDocument();
178
179 // Create a provisional root node,
180 // but only assign it to the result (ie to get a non-null result)
181 // when we know that there is some real data in there.
182 final Element root = doc.createElement(TAG_NAME_METADATA_TOP);
183 doc.appendChild(root);
184
185
186 // The "misc" node contains misc items.
187 final Element misc = doc.createElement("misc");
188 root.appendChild(misc);
189 result = root; // Got some real data...
190
191 final Element compNode = doc.createElement("compressed");
192 compNode.setAttribute("value", String.valueOf(md.compressed));
193 misc.appendChild(compNode);
194
195 final Element versionNode = doc.createElement("version");
196 versionNode.setAttribute("value", String.valueOf(md.version));
197 misc.appendChild(versionNode);
198
199 final Element lengthNode = doc.createElement("length");
200 lengthNode.setAttribute("value", String.valueOf(md.length));
201 misc.appendChild(lengthNode);
202
203
204 // The "audio" node contains data on any soundtrack.
205 final Element audio = doc.createElement("audio");
206 root.appendChild(audio);
207 result = root; // Got some real data...
208
209
210 // The "video" node contains data on any imagery.
211 final Element video = doc.createElement("video");
212 root.appendChild(video);
213 result = root; // Got some real data...
214
215 final Element xminNode = doc.createElement("xmin");
216 xminNode.setAttribute("value", String.valueOf(md.xmin));
217 video.appendChild(xminNode);
218
219 final Element xmaxNode = doc.createElement("xmax");
220 xmaxNode.setAttribute("value", String.valueOf(md.xmax));
221 video.appendChild(xmaxNode);
222
223 final Element yminNode = doc.createElement("ymin");
224 yminNode.setAttribute("value", String.valueOf(md.ymin));
225 video.appendChild(yminNode);
226
227 final Element ymaxNode = doc.createElement("ymax");
228 ymaxNode.setAttribute("value", String.valueOf(md.ymax));
229 video.appendChild(ymaxNode);
230
231 final Element rateNode = doc.createElement("fps");
232 rateNode.setAttribute("value", String.valueOf(md.rate));
233 video.appendChild(rateNode);
234
235 final Element countNode = doc.createElement("count");
236 countNode.setAttribute("value", String.valueOf(md.count));
237 video.appendChild(countNode);
238 }
239 catch(final ParserConfigurationException e)
240 {
241 e.printStackTrace();
242 return(null); // Cannot build document...
243 }
244 catch(final IOException e)
245 {
246 e.printStackTrace();
247 return(null); // Cannot parse exhibit file...
248 }
249
250 return(result);
251 }
252 }