/* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * FlashInfo is (c) 2006 Paul Brooks Andrus and is released under the MIT * License: http://www.opensource.org/licenses/mit-license.php */ package org.riotfamily.media.meta; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author brooks */ public class FlashInfo { protected Logger log = LoggerFactory.getLogger(getClass()); public static String COMPRESSED = "compressed"; public static String UNCOMPRESSED = "uncompressed"; private String signature; private String compressionType; private int version; private long size; private int nbits; private int xmax; private int ymax; private int width; private int height; private int frameRate; private int frameCount; private boolean valid = false; public FlashInfo(File file) throws IOException { valid = parseHeader(file); } public boolean parseHeader(File file) throws IOException { log.debug("Parsing file '" + file + "'..."); FileInputStream fis = null; byte[] temp = new byte[(int) file.length()]; byte[] swf = null; fis = new FileInputStream(file); fis.read(temp); fis.close(); if (!isSWF(temp)) { log.debug("The file does not appear to be a swf - incorrect file signature"); return false; } else { signature = "" + (char) temp[0] + (char) temp[1] + (char) temp[2]; } if (isCompressed(temp[0])) { try { swf = decompress(temp); } catch (DataFormatException dfe) { log.warn("The file appears to be compressed, but decompression failed: " + dfe.getMessage()); return false; } compressionType = FlashInfo.COMPRESSED; } else { swf = temp; compressionType = FlashInfo.UNCOMPRESSED; } // version is the 4th byte of a swf; version = swf[3]; // bytes 5 - 8 represent the size in bytes of a swf size = readSize(swf); // Stage dimensions are stored in a rect nbits = ((swf[8] & 0xff) >> 3); PackedBitObj pbo = readPackedBits(swf, 8, 5, nbits); PackedBitObj pbo2 = readPackedBits(swf, pbo.nextByteIndex, pbo.nextBitIndex, nbits); PackedBitObj pbo3 = readPackedBits(swf, pbo2.nextByteIndex, pbo2.nextBitIndex, nbits); PackedBitObj pbo4 = readPackedBits(swf, pbo3.nextByteIndex, pbo3.nextBitIndex, nbits); xmax = pbo2.value; ymax = pbo4.value; width = convertTwipsToPixels(xmax); height = convertTwipsToPixels(ymax); int bytePointer = pbo4.nextByteIndex + 2; frameRate = swf[bytePointer]; bytePointer++; int fc1 = swf[bytePointer] & 0xFF; bytePointer++; int fc2 = swf[bytePointer] & 0xFF; bytePointer++; frameCount = (fc2 << 8) + fc1; log.debug("signature: " + getSignature()); log.debug("version: " + getVersion()); log.debug("compression: " + getCompressionType()); log.debug("size: " + getSize()); log.debug("nbits: " + getNbits()); log.debug("xmax: " + getXmax()); log.debug("ymax: " + getYmax()); log.debug("width: " + getWidth()); log.debug("height: " + getHeight()); log.debug("frameRate: " + getFrameRate()); log.debug("frameCount: " + getFrameCount()); return true; } public void read(byte[] output, byte[] input, int offset) { System.arraycopy(input, offset, output, 0, output.length - offset); } public int readSize(byte[] bytes) { long size = 0; for (int i = 0; i < 4; i++) { size |= bytes[7 - i] & 0xFF; if (i < 3) { size <<= 8; } } return (int) size; } public PackedBitObj readPackedBits(byte[] bytes, int byteMarker, int bitMarker, int length) { int total = 0; int shift = 7 - bitMarker; int counter = 0; int bitIndex = bitMarker; int byteIndex = byteMarker; while (counter < length) { for (int i = bitMarker; i < 8; i++) { int bit = ((bytes[byteMarker] & 0xff) >> shift) & 1; total = (total << 1) + bit; bitIndex = i; shift--; counter++; if (counter == length) { break; } } byteIndex = byteMarker; byteMarker++; bitMarker = 0; shift = 7; } return new PackedBitObj(bitIndex, byteIndex, total); } public int convertTwipsToPixels(int twips) { return twips / 20; } public int convertPixelsToTwips(int pixels) { return pixels * 20; } public boolean isSWF(byte[] signature) { String sig = "" + (char) signature[0] + (char) signature[1] + (char) signature[2]; if (sig.equals("FWS") || sig.equals("CWS")) { return true; } else { return false; } } public boolean isCompressed(int firstByte) { if (firstByte == 67) { return true; } else { return false; } } public byte[] compress(byte[] bytes, int length) { byte[] compressed = null; byte[] temp = new byte[length]; Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION); deflater.setInput(bytes, 8, length - 8); deflater.finish(); int compressedLength = deflater.deflate(temp); compressed = new byte[compressedLength + 8]; // the first 8 bytes of the header are uncompressed System.arraycopy(bytes, 0, compressed, 0, 8); // copy the compressed data from the temporary byte array to its new // byte array System.arraycopy(temp, 0, compressed, 8, compressedLength); // the first byte of the swf indicates the swf is compressed temp[0] = 67; // make sure the swf size structure represents the uncompressed size of // the swf int bl = bytes.length; temp[4] = (byte) (bl & 0x000000FF); temp[5] = (byte) ((bl & 0x0000FF00) >> 8); temp[6] = (byte) ((bl & 0x00FF0000) >> 16); temp[7] = (byte) ((bl & 0xFF000000) >> 24); return compressed; } public byte[] decompress(byte[] bytes) throws DataFormatException { int size = readSize(bytes); byte[] uncompressed = new byte[size]; System.arraycopy(bytes, 0, uncompressed, 0, 8); Inflater inflater = new Inflater(); inflater.setInput(bytes, 8, bytes.length - 8); inflater.inflate(uncompressed, 8, size - 8); inflater.finished(); // the first byte of the swf indicates the swf is uncompressed uncompressed[0] = 70; return uncompressed; } /** * @return the frameCount */ public int getFrameCount() { return frameCount; } /** * @return the frameRate */ public int getFrameRate() { return frameRate; } /** * @return the nbits */ public int getNbits() { return nbits; } /** * @return the signature */ public String getSignature() { return signature; } /** * @return the size */ public long getSize() { return size; } /** * @return the version */ public int getVersion() { return version; } /** * @return the xmax */ public int getXmax() { return xmax; } /** * @return the ymax */ public int getYmax() { return ymax; } /** * @return the compressionType */ public String getCompressionType() { return compressionType; } /** * @return the height */ public int getHeight() { return height; } /** * @return the width */ public int getWidth() { return width; } /** * @return true if no exception occured during parsing */ public boolean isValid() { return valid; } /** * @author brooks */ public static class PackedBitObj { public int bitIndex = 0; public int byteIndex = 0; public int value = 0; public int nextBitIndex = 0; public int nextByteIndex = 0; public int nextByteBoundary = 0; /** * @param bitIndex The index of the last bit read * @param byteMarker The index of the last byte read * @param decimalValue The decimal value of the packed bit sequence * @param binaryString */ public PackedBitObj(int bitMarker, int byteMarker, int decimalValue) { bitIndex = bitMarker; byteIndex = byteMarker; value = decimalValue; nextBitIndex = bitMarker; if (bitMarker < 7) { nextBitIndex++; nextByteIndex = byteMarker; nextByteBoundary = byteMarker++; } else { nextBitIndex = 0; nextByteIndex++; nextByteBoundary = nextByteIndex; } } } }