View Javadoc

1   /***
2    * SWFHeader is (c) 2006 Paul Brooks Andrus and is released under the MIT License:
3    * http://www.opensource.org/licenses/mit-license.php
4    * 
5    * http://www.brooksandrus.com/blog/2006/08/01/lightweight-swf-header-reader-java/
6    * com.brooksandrus.utils.swf
7    * 
8    * Modified to efficiently read only the swf header (Steve Milek)
9    * 
10   */
11  package org.apache.portals.gems.flash;
12  
13  import java.io.*;
14  import java.util.zip.*;
15  
16  import org.apache.commons.logging.Log;
17  import org.apache.commons.logging.LogFactory;
18  
19  
20  /***
21   * @author brooks
22   * 
23   */
24  public class SWFHeader
25  {
26      protected Log log = LogFactory.getLog( SWFHeader.class );
27      public static final String COMPRESSED   = "compressed";
28      public static final String UNCOMPRESSED = "uncompressed";
29      
30      private String       signature;
31      private String       compressionType;
32      private int          version;
33      private long         size;
34      private int          nbits;
35      private int          xmax;
36      private int          ymax;
37      private int          width;
38      private int          height;
39      private int          frameRate;
40      private int          frameCount;
41  
42      public SWFHeader()
43      {
44          super();
45      }   
46     
47      public boolean parseHeader( String fileName )
48      {
49          if ( fileName == null )
50          {
51              log.error( "Name for SWF file is null" );
52              return false;
53          }
54          return manageInputStreamAndParseHeader( null, new File( fileName ) );
55      }
56     
57      public boolean parseHeader( File file )
58      {
59          return manageInputStreamAndParseHeader( null, file );
60      }
61     
62      public boolean parseHeader( InputStream is )
63      {
64          return manageInputStreamAndParseHeader( is, null );
65      }
66     
67      private boolean manageInputStreamAndParseHeader( InputStream is, File file )
68      {
69          boolean inputIsSWF = false;
70          try
71          {
72              if ( is == null && file != null )
73              {
74                  is = new FileInputStream( file );
75              }
76              inputIsSWF = doParseHeader( is );
77          }
78          catch ( FileNotFoundException fnfEx )
79          {
80              log.error( "SWF file could not be found", fnfEx );
81              inputIsSWF = false;
82          }
83          catch ( Exception e )
84          {
85              log.error( "Failed to parse SWF input", e );
86              inputIsSWF = false;
87          }
88          finally
89          {
90              try
91              {
92                  if ( is != null )
93                  {
94                      is.close();
95                  }
96              }
97              catch ( Exception ex )
98              {
99                  log.error( "Failed to close SWF InputStream", ex );
100                 inputIsSWF = false;
101             }
102         }
103         return inputIsSWF;
104     }
105   
106     private boolean doParseHeader( InputStream is ) throws Exception
107     {
108         byte[] temp = new byte[128];   // header is 47 bytes - we must read more in case compression is used (see uncompressHeader comments)
109         
110         byte[] swf = null;
111 
112         is.read( temp );
113 
114         if ( !isSWF( temp ) )
115         {
116             log.error( "Input does not match SWF format - incorrect file signature" );
117             return false;
118         }
119         else
120         {
121             signature = "" + ( char ) temp[0] + ( char ) temp[1]
122                 + ( char ) temp[2];
123         }
124 
125         if ( isCompressed( temp[0] ) )
126         {
127             swf = uncompressHeader( temp );
128             compressionType = SWFHeader.COMPRESSED;
129         }
130         else
131         {
132             swf = temp;
133             compressionType = SWFHeader.UNCOMPRESSED;
134         }
135 
136         //System.out.println( "swf byte array length: " + swf.length );
137        
138         // version is the 4th byte of a swf; 
139         version = swf[3];
140 
141         // bytes 5 - 8 represent the size in bytes of a swf
142         size = readSize( swf );
143 
144         // Stage dimensions are stored in a rect
145 
146         nbits = ( ( swf[8] & 0xff ) >> 3 );
147 
148         PackedBitObj pbo = readPackedBits( swf, 8, 5, nbits );
149        
150         PackedBitObj pbo2 = readPackedBits( swf, pbo.nextByteIndex,
151                                             pbo.nextBitIndex, nbits );
152 
153         PackedBitObj pbo3 = readPackedBits( swf, pbo2.nextByteIndex,
154                                             pbo2.nextBitIndex, nbits );
155 
156         PackedBitObj pbo4 = readPackedBits( swf, pbo3.nextByteIndex,
157                                             pbo3.nextBitIndex, nbits );
158 
159         xmax = pbo2.value;
160         ymax = pbo4.value;
161 
162         width = convertTwipsToPixels( xmax );
163         height = convertTwipsToPixels( ymax );
164 
165         int bytePointer = pbo4.nextByteIndex + 2;
166 
167         frameRate = swf[bytePointer];
168         bytePointer++;
169        
170        
171         int fc1 = swf[bytePointer] & 0xFF;
172         bytePointer++;
173        
174         int fc2 = swf[bytePointer] & 0xFF;
175         bytePointer++;
176        
177         frameCount = ( fc2 << 8 ) + fc1;
178        
179         dumpHeaderToStdOut();
180 
181         return true;
182     }
183 
184     public void read( byte[] output, byte[] input, int offset )
185     {
186         System.arraycopy( input, offset, output, 0, output.length - offset );
187     }
188 
189     public PackedBitObj readPackedBits( byte[] bytes, int byteMarker,
190                                         int bitMarker, int length )
191     {
192         int total = 0;
193         int shift = 7 - bitMarker;
194         int counter = 0;
195         int bitIndex = bitMarker;
196         int byteIndex = byteMarker;
197       
198         while ( counter < length )
199         {
200             for ( int i = bitMarker; i < 8; i++ )
201             {
202                 int bit = ( ( bytes[byteMarker] & 0xff ) >> shift ) & 1;
203                 total = ( total << 1 ) + bit;
204                 bitIndex = i;
205                 shift--;
206                 counter++;
207             
208                 if ( counter == length )
209                 {
210                     break;
211                 }
212             }
213             byteIndex = byteMarker;
214             byteMarker++;
215             bitMarker = 0;
216             shift = 7;
217         }
218         return new PackedBitObj( bitIndex, byteIndex, total );
219     }
220 
221     public int convertTwipsToPixels( int twips )
222     {
223         return twips / 20;
224     }
225 
226     public int convertPixelsToTwips( int pixels )
227     {
228         return pixels * 20;
229     }
230 
231     public boolean isSWF( byte[] signature )
232     {
233         String sig = "" + ( char ) signature[0] + ( char ) signature[1]
234             + ( char ) signature[2];
235 
236         if ( sig.equals( "FWS" ) || sig.equals( "CWS" ) )
237         {
238             return true;
239         }
240         else
241         {
242             return false;
243         }
244     }
245 
246     public boolean isCompressed( int firstByte )
247     {
248         if ( firstByte == 67 )
249         {
250             return true;
251         }
252         else
253         {
254             return false;
255         }
256     }
257    
258     public boolean isCompressed()
259     {
260         boolean result = false;
261         if ( signature.equalsIgnoreCase( "CWS" ) )
262         {
263             result = true;
264         }
265         return result;
266     }
267 
268     // Inflator class will be upset if we let it read to the end of input and it discovers that
269     // the zip format is not properly terminated - therefore, we must provide Inflater with more input
270     // than what we intend to inflate
271     protected byte[] uncompressHeader( byte[] bytes ) throws DataFormatException
272     {  
273         Inflater decompressor = new Inflater();
274         byte[] compressed = strip( bytes );
275         decompressor.setInput( compressed );   // feed the Inflater the bytes
276         byte[] buffer = new byte[ 56 ];
277         int count = decompressor.inflate( buffer );   // decompress the data into the buffer
278         decompressor.end();
279         
280         //create an array to hold the header and body bytes
281         byte[] swf = new byte[ 8 + count ];
282         //copy the first 8 bytes which are uncompressed into the swf array
283         System.arraycopy( bytes, 0, swf, 0, 8 );
284         //copy the uncompressed data into the swf array
285         System.arraycopy( buffer, 0, swf, 8, count );
286         //the first byte of the swf indicates whether the swf is compressed or not
287         swf[0] = 70;
288       
289         return swf;
290     }
291 
292     // This version of uncompressHeader may be safer (because it uses InflaterInputStream),
293     // but until there is some evidence of this we will stick with the direct approach of
294     // using Inflater (above).
295     /*
296       protected byte[] uncompressHeader( byte[] headerBytes ) throws IOException
297       {
298       byte[] compressed = strip( headerBytes );
299       InflaterInputStream iis = new InflaterInputStream( new ByteArrayInputStream( compressed ) );
300       byte[] buffer = new byte[56];
301       int bytesRead = iis.read( buffer, 0, buffer.length );
302       iis.close();
303 
304       byte[] swfHeader = new byte[ 8 + bytesRead ];
305         
306       // copy the first 8 bytes which are uncompressed into the swf array
307       System.arraycopy( headerBytes, 0, swfHeader, 0, 8 );
308         
309       // copy the uncompressed data into the swf array
310       System.arraycopy( buffer, 0, swfHeader, 8, bytesRead );
311         
312       // the first byte of the swf indicates whether the swf is compressed or not
313       swfHeader[0] = 70;
314         
315       return swfHeader;
316       }
317     */
318 
319     public int readSize( byte[] bytes )
320     {
321         int s = 0;
322         for ( int i = 0; i < 4; i++ )
323         {
324             s = ( s << 8 ) + bytes[i + 4];
325         }
326 
327         // We can use Integer.reverseBytes(int) in Java 1.5+.
328         //s = Integer.reverseBytes( s ) - 1;
329         s = (((s >>> 24)           ) |
330             ((s >>   8) &   0xFF00) |
331             ((s <<   8) & 0xFF0000) |
332             ((s << 24))) - 1;
333 
334         return s;
335     }
336     
337     public byte[] strip( byte[] bytes )
338     {
339         byte[] compressable = new byte[bytes.length - 8];
340         System.arraycopy( bytes, 8, compressable, 0, bytes.length - 8 );//fills a byte array with data needing decompression
341         return compressable;
342     }
343 
344     
345     /***
346      * @param args
347      */
348     public static void main( String[] args )
349     {
350         if ( args.length != 1 )
351         {
352             System.err.println( "usage: swf_file" );
353         }
354         else
355         {
356             try
357             {
358                 SWFHeader swfH = new SWFHeader();
359                 if ( swfH.parseHeader( args[0] ) )
360                 {
361                     swfH.dumpHeaderToStdOut();
362                 }
363             }
364             catch ( Exception e )
365             {
366                 System.err.println( e.getMessage() );
367             }
368         }
369         
370     }
371 
372     public void dumpHeaderToStdOut()
373     {
374         System.out.println( "signature:   " + getSignature() );
375         System.out.println( "version:     " + getVersion() );
376         System.out.println( "compression: " + getCompressionType() );
377         System.out.println( "size:        " + getSize() );
378         System.out.println( "nbits:       " + getNbits() );
379         System.out.println( "xmax:        " + getXmax() );
380         System.out.println( "ymax:        " + getYmax() );
381         System.out.println( "width:       " + getWidth() );
382         System.out.println( "height:      " + getHeight() );
383         System.out.println( "frameRate:   " + getFrameRate() );
384         System.out.println( "frameCount:  " + getFrameCount() );
385     }
386 
387     /***
388      * @return the frameCount
389      */
390     public int getFrameCount()
391     {
392         return frameCount;
393     }
394 
395     /***
396      * @return the frameRate
397      */
398     public int getFrameRate()
399     {
400         return frameRate;
401     }
402 
403     /***
404      * @return the nbits
405      */
406     public int getNbits()
407     {
408         return nbits;
409     }
410 
411     /***
412      * @return the signature
413      */
414     public String getSignature()
415     {
416         return signature;
417     }
418 
419     /***
420      * @return the size
421      */
422     public long getSize()
423     {
424         return size;
425     }
426 
427     /***
428      * @return the version
429      */
430     public int getVersion()
431     {
432         return version;
433     }
434 
435     /***
436      * @return the xmax
437      */
438     public int getXmax()
439     {
440         return xmax;
441     }
442 
443     /***
444      * @return the ymax
445      */
446     public int getYmax()
447     {
448         return ymax;
449     }
450 
451     /***
452      * @return the compressionType
453      */
454     public String getCompressionType()
455     {
456         return compressionType;
457     }
458 
459     /***
460      * @return the height
461      */
462     public int getHeight()
463     {
464         return height;
465     }
466 
467     /***
468      * @return the width
469      */
470     public int getWidth()
471     {
472         return width;
473     }
474 
475 }