001: /**
002: * SWFHeader is (c) 2006 Paul Brooks Andrus and is released under the MIT License:
003: * http://www.opensource.org/licenses/mit-license.php
004: *
005: * http://www.brooksandrus.com/blog/2006/08/01/lightweight-swf-header-reader-java/
006: * com.brooksandrus.utils.swf
007: *
008: * Modified to efficiently read only the swf header (Steve Milek)
009: *
010: */package org.apache.portals.gems.flash;
011:
012: import java.io.*;
013: import java.util.zip.*;
014:
015: import org.apache.commons.logging.Log;
016: import org.apache.commons.logging.LogFactory;
017:
018: /**
019: * @author brooks
020: *
021: */
022: public class SWFHeader {
023: protected Log log = LogFactory.getLog(SWFHeader.class);
024: public static final String COMPRESSED = "compressed";
025: public static final String UNCOMPRESSED = "uncompressed";
026:
027: private String signature;
028: private String compressionType;
029: private int version;
030: private long size;
031: private int nbits;
032: private int xmax;
033: private int ymax;
034: private int width;
035: private int height;
036: private int frameRate;
037: private int frameCount;
038:
039: public SWFHeader() {
040: super ();
041: }
042:
043: public boolean parseHeader(String fileName) {
044: if (fileName == null) {
045: log.error("Name for SWF file is null");
046: return false;
047: }
048: return manageInputStreamAndParseHeader(null, new File(fileName));
049: }
050:
051: public boolean parseHeader(File file) {
052: return manageInputStreamAndParseHeader(null, file);
053: }
054:
055: public boolean parseHeader(InputStream is) {
056: return manageInputStreamAndParseHeader(is, null);
057: }
058:
059: private boolean manageInputStreamAndParseHeader(InputStream is,
060: File file) {
061: boolean inputIsSWF = false;
062: try {
063: if (is == null && file != null) {
064: is = new FileInputStream(file);
065: }
066: inputIsSWF = doParseHeader(is);
067: } catch (FileNotFoundException fnfEx) {
068: log.error("SWF file could not be found", fnfEx);
069: inputIsSWF = false;
070: } catch (Exception e) {
071: log.error("Failed to parse SWF input", e);
072: inputIsSWF = false;
073: } finally {
074: try {
075: if (is != null) {
076: is.close();
077: }
078: } catch (Exception ex) {
079: log.error("Failed to close SWF InputStream", ex);
080: inputIsSWF = false;
081: }
082: }
083: return inputIsSWF;
084: }
085:
086: private boolean doParseHeader(InputStream is) throws Exception {
087: byte[] temp = new byte[128]; // header is 47 bytes - we must read more in case compression is used (see uncompressHeader comments)
088:
089: byte[] swf = null;
090:
091: is.read(temp);
092:
093: if (!isSWF(temp)) {
094: log
095: .error("Input does not match SWF format - incorrect file signature");
096: return false;
097: } else {
098: signature = "" + (char) temp[0] + (char) temp[1]
099: + (char) temp[2];
100: }
101:
102: if (isCompressed(temp[0])) {
103: swf = uncompressHeader(temp);
104: compressionType = SWFHeader.COMPRESSED;
105: } else {
106: swf = temp;
107: compressionType = SWFHeader.UNCOMPRESSED;
108: }
109:
110: //System.out.println( "swf byte array length: " + swf.length );
111:
112: // version is the 4th byte of a swf;
113: version = swf[3];
114:
115: // bytes 5 - 8 represent the size in bytes of a swf
116: size = readSize(swf);
117:
118: // Stage dimensions are stored in a rect
119:
120: nbits = ((swf[8] & 0xff) >> 3);
121:
122: PackedBitObj pbo = readPackedBits(swf, 8, 5, nbits);
123:
124: PackedBitObj pbo2 = readPackedBits(swf, pbo.nextByteIndex,
125: pbo.nextBitIndex, nbits);
126:
127: PackedBitObj pbo3 = readPackedBits(swf, pbo2.nextByteIndex,
128: pbo2.nextBitIndex, nbits);
129:
130: PackedBitObj pbo4 = readPackedBits(swf, pbo3.nextByteIndex,
131: pbo3.nextBitIndex, nbits);
132:
133: xmax = pbo2.value;
134: ymax = pbo4.value;
135:
136: width = convertTwipsToPixels(xmax);
137: height = convertTwipsToPixels(ymax);
138:
139: int bytePointer = pbo4.nextByteIndex + 2;
140:
141: frameRate = swf[bytePointer];
142: bytePointer++;
143:
144: int fc1 = swf[bytePointer] & 0xFF;
145: bytePointer++;
146:
147: int fc2 = swf[bytePointer] & 0xFF;
148: bytePointer++;
149:
150: frameCount = (fc2 << 8) + fc1;
151:
152: dumpHeaderToStdOut();
153:
154: return true;
155: }
156:
157: public void read(byte[] output, byte[] input, int offset) {
158: System.arraycopy(input, offset, output, 0, output.length
159: - offset);
160: }
161:
162: public PackedBitObj readPackedBits(byte[] bytes, int byteMarker,
163: int bitMarker, int length) {
164: int total = 0;
165: int shift = 7 - bitMarker;
166: int counter = 0;
167: int bitIndex = bitMarker;
168: int byteIndex = byteMarker;
169:
170: while (counter < length) {
171: for (int i = bitMarker; i < 8; i++) {
172: int bit = ((bytes[byteMarker] & 0xff) >> shift) & 1;
173: total = (total << 1) + bit;
174: bitIndex = i;
175: shift--;
176: counter++;
177:
178: if (counter == length) {
179: break;
180: }
181: }
182: byteIndex = byteMarker;
183: byteMarker++;
184: bitMarker = 0;
185: shift = 7;
186: }
187: return new PackedBitObj(bitIndex, byteIndex, total);
188: }
189:
190: public int convertTwipsToPixels(int twips) {
191: return twips / 20;
192: }
193:
194: public int convertPixelsToTwips(int pixels) {
195: return pixels * 20;
196: }
197:
198: public boolean isSWF(byte[] signature) {
199: String sig = "" + (char) signature[0] + (char) signature[1]
200: + (char) signature[2];
201:
202: if (sig.equals("FWS") || sig.equals("CWS")) {
203: return true;
204: } else {
205: return false;
206: }
207: }
208:
209: public boolean isCompressed(int firstByte) {
210: if (firstByte == 67) {
211: return true;
212: } else {
213: return false;
214: }
215: }
216:
217: public boolean isCompressed() {
218: boolean result = false;
219: if (signature.equalsIgnoreCase("CWS")) {
220: result = true;
221: }
222: return result;
223: }
224:
225: // Inflator class will be upset if we let it read to the end of input and it discovers that
226: // the zip format is not properly terminated - therefore, we must provide Inflater with more input
227: // than what we intend to inflate
228: protected byte[] uncompressHeader(byte[] bytes)
229: throws DataFormatException {
230: Inflater decompressor = new Inflater();
231: byte[] compressed = strip(bytes);
232: decompressor.setInput(compressed); // feed the Inflater the bytes
233: byte[] buffer = new byte[56];
234: int count = decompressor.inflate(buffer); // decompress the data into the buffer
235: decompressor.end();
236:
237: //create an array to hold the header and body bytes
238: byte[] swf = new byte[8 + count];
239: //copy the first 8 bytes which are uncompressed into the swf array
240: System.arraycopy(bytes, 0, swf, 0, 8);
241: //copy the uncompressed data into the swf array
242: System.arraycopy(buffer, 0, swf, 8, count);
243: //the first byte of the swf indicates whether the swf is compressed or not
244: swf[0] = 70;
245:
246: return swf;
247: }
248:
249: // This version of uncompressHeader may be safer (because it uses InflaterInputStream),
250: // but until there is some evidence of this we will stick with the direct approach of
251: // using Inflater (above).
252: /*
253: protected byte[] uncompressHeader( byte[] headerBytes ) throws IOException
254: {
255: byte[] compressed = strip( headerBytes );
256: InflaterInputStream iis = new InflaterInputStream( new ByteArrayInputStream( compressed ) );
257: byte[] buffer = new byte[56];
258: int bytesRead = iis.read( buffer, 0, buffer.length );
259: iis.close();
260:
261: byte[] swfHeader = new byte[ 8 + bytesRead ];
262:
263: // copy the first 8 bytes which are uncompressed into the swf array
264: System.arraycopy( headerBytes, 0, swfHeader, 0, 8 );
265:
266: // copy the uncompressed data into the swf array
267: System.arraycopy( buffer, 0, swfHeader, 8, bytesRead );
268:
269: // the first byte of the swf indicates whether the swf is compressed or not
270: swfHeader[0] = 70;
271:
272: return swfHeader;
273: }
274: */
275:
276: public int readSize(byte[] bytes) {
277: int s = 0;
278: for (int i = 0; i < 4; i++) {
279: s = (s << 8) + bytes[i + 4];
280: }
281:
282: // We can use Integer.reverseBytes(int) in Java 1.5+.
283: //s = Integer.reverseBytes( s ) - 1;
284: s = (((s >>> 24)) | ((s >> 8) & 0xFF00) | ((s << 8) & 0xFF0000) | ((s << 24))) - 1;
285:
286: return s;
287: }
288:
289: public byte[] strip(byte[] bytes) {
290: byte[] compressable = new byte[bytes.length - 8];
291: System.arraycopy(bytes, 8, compressable, 0, bytes.length - 8);//fills a byte array with data needing decompression
292: return compressable;
293: }
294:
295: /**
296: * @param args
297: */
298: public static void main(String[] args) {
299: if (args.length != 1) {
300: System.err.println("usage: swf_file");
301: } else {
302: try {
303: SWFHeader swfH = new SWFHeader();
304: if (swfH.parseHeader(args[0])) {
305: swfH.dumpHeaderToStdOut();
306: }
307: } catch (Exception e) {
308: System.err.println(e.getMessage());
309: }
310: }
311:
312: }
313:
314: public void dumpHeaderToStdOut() {
315: System.out.println("signature: " + getSignature());
316: System.out.println("version: " + getVersion());
317: System.out.println("compression: " + getCompressionType());
318: System.out.println("size: " + getSize());
319: System.out.println("nbits: " + getNbits());
320: System.out.println("xmax: " + getXmax());
321: System.out.println("ymax: " + getYmax());
322: System.out.println("width: " + getWidth());
323: System.out.println("height: " + getHeight());
324: System.out.println("frameRate: " + getFrameRate());
325: System.out.println("frameCount: " + getFrameCount());
326: }
327:
328: /**
329: * @return the frameCount
330: */
331: public int getFrameCount() {
332: return frameCount;
333: }
334:
335: /**
336: * @return the frameRate
337: */
338: public int getFrameRate() {
339: return frameRate;
340: }
341:
342: /**
343: * @return the nbits
344: */
345: public int getNbits() {
346: return nbits;
347: }
348:
349: /**
350: * @return the signature
351: */
352: public String getSignature() {
353: return signature;
354: }
355:
356: /**
357: * @return the size
358: */
359: public long getSize() {
360: return size;
361: }
362:
363: /**
364: * @return the version
365: */
366: public int getVersion() {
367: return version;
368: }
369:
370: /**
371: * @return the xmax
372: */
373: public int getXmax() {
374: return xmax;
375: }
376:
377: /**
378: * @return the ymax
379: */
380: public int getYmax() {
381: return ymax;
382: }
383:
384: /**
385: * @return the compressionType
386: */
387: public String getCompressionType() {
388: return compressionType;
389: }
390:
391: /**
392: * @return the height
393: */
394: public int getHeight() {
395: return height;
396: }
397:
398: /**
399: * @return the width
400: */
401: public int getWidth() {
402: return width;
403: }
404:
405: }
|