import java.io.*;

/***************************************************************************

    <p><i>DumpQuicktime:</i> Dumps a Quicktime movie file to stdout.</p>


    <p><i>Development Environment</i></p>

      <li>Compiled under JDK 1.3.1</li>

 ***************************************************************************/

    public class DumpQuicktime
      {

/**   Holds an array of atoms and their descriptions.  Each element in the
      array represents one atom.  The first 4 characters are the atom ID.
      The remainder of the string is the description.  *//*
      ==================================================================== */

      public final static String[] masAtomsAndDescriptions =
        {
        /*=================================================================*/
        "???? -- Unknown Atom",                    
        "clip -- Clipping",                        
        "crgn -- Clipping Region",                 
        "dinf -- Data Information",                
        "dref -- Data Reference",                  
        "edts -- Edit",                            
        "elst -- Edit List",                       
        "esds -- ESDS Atom (is 4 bytes longer than the length says it should be)",
        "free -- Unused space in the movie data file",
        "hdlr -- Handler Reference",               
        "kmat -- Compressed Matte",                
        "matt -- Track Matte",                     
        "mdat -- Movie Data",                      
        "mdhd -- Media Header",                    
        "mdia -- Media",                           
        "minf -- Media Information",               
        "moov -- Movie",                           
        "mvhd -- Movie Header",                    
        "skip -- Unused space in the movie data file",
        "smhd -- Sound Media Information Header",  
        "stbl -- Sample Table",                    
        "stco -- Chunk Offset",                    
        "stsc -- Sample-to-Chunk",                 
        "stsd -- Sample Description",              
        "stsh -- Shadow Sync",                     
        "stss -- Sync Sample",                     
        "stsz -- Sample Size",                     
        "stts -- Time-to-Sample",                  
        "tkhd -- Track Header",                    
        "trak -- Track",                           
        "udta -- User-defined Data",               
        "vmhd -- Video Media Information Header",  
        "wide -- Wide File [Data & Resources]",    
        /*=================================================================*/
        };


/**   Holds the quicktime file in memory. *//*
      ========================================  */

      byte[] mabBuffer;



/*=========================================================================*
                         Construction/Initialization
 *=========================================================================*/



/*    D U M P   Q U I C K T I M E
 */
/**   
 */
public DumpQuicktime (String sFilename)
      {
      File f;
      int iSize;
      FileInputStream fis;

      try
        {
        f = new File (sFilename);
        iSize = (int) f.length (); 
        mabBuffer = new byte[iSize];
        fis = new FileInputStream (f);
        fis.read (mabBuffer);
        fis.close ();
        }
      catch (Throwable t)
        {
        System.out.println ("(" + sFilename + ") unable to load the file");
        }

      }



/*    M A I N
 */
/**   The mainline routine.  If this is run without commandline parameters
 *    then a chart of the Quicktime atoms is printed out along with a
 *    description of syntax to use to run the program.
 *
 */
public static void main (String[] asArgsIn)
      {
      int i;
      DumpQuicktime x;

      if (asArgsIn.length >= 1)
        {
        x = new DumpQuicktime (asArgsIn[0]);
        x.dumpMovie (asArgsIn[0]);
        }

      System.out.println ("\n*-------------Quicktime Atom Chart-------------*");
      for (i = 1; i < masAtomsAndDescriptions.length; i++)
        {
        System.out.println ("    " + masAtomsAndDescriptions[i]);
        }
      System.out.println ("*----------------------------------------------*");

      System.out.println ("\n    To dump a quicktime movie, provide the "
        + "filename as a parameter:\n");
      System.out.println ("      Usage: java DumpQuicktime moviename.mov\n");
      System.out.println ("*----------------------------------------------*");
      }



/*=========================================================================*
              The following methods are in alphabetical order
 *=========================================================================*/



/*    D U M P   A T O M
 */
/**   Dumps a single container atom or leaf atom.  If the atom is a 
 *    container, this method recurses.  If the atom is a leaf, the output
 *    is written to standard out.
 *
 */
private int dumpAtom (DataInputStream disIn, int iAtomLengthIn, 
      String sMarginIn)
      {
      int i;
      int iSize, iSize2;
      int iSampleSize;
      int iLength = 0;
      int iTotalLength = 0;
      int iAtomID;
      String s, s1;
      String sAtomID;
      byte[] abAtomID = new byte[4];
      byte[] ab4 = new byte[4];
      byte[] ab32 = new byte[32];
      String sLine = "- - - - - - - - - - - - - - - - - - - - - - - - - - - "
        + "- - - - - - - -";


/*    Each cycle of this loop dumps one atom.
      =======================================  */

      while (iTotalLength < iAtomLengthIn)
        {
        try
          {


/*        Read the length and atom id.
          ============================  */

          iLength = disIn.readInt ();                      // Length
          iTotalLength += iLength;
          disIn.read (abAtomID);                           // Atom ID
          sAtomID = new String (abAtomID);
          iAtomID = getAtomID (sAtomID);
          System.out.println (sMarginIn + "Atom = " + sAtomID + " -- "
            + getAtomDescription (iAtomID));
          System.out.println (sMarginIn + "Size = " + iLength);
          if ((iLength <= 0) || (iLength >= mabBuffer.length))
            {
            System.out.println ("Atom length error, halting");
            System.exit (1);
            }


/*        If the atom is a container atom, recurse to dump it.
          ====================================================  */
  
          if (isContainerAtom (sAtomID)) 
            {                           
            sMarginIn += "  ";
            dumpAtom (disIn, iLength - 8, sMarginIn);
            sMarginIn = sMarginIn.substring (0, sMarginIn.length () - 2);
            continue;
            }


/*        Process one leaf atom depending on its atom id.
          ===============================================  */

          if (sAtomID.equals ("mvhd"))
            {
            System.out.println (sMarginIn + sLine);
            System.out.println (sMarginIn + "Version(1) Flags(3)  = 0x" 
              + Integer.toString (disIn.readInt (), 16));
            System.out.println (sMarginIn + "Creation Time        = " 
              + ((long) disIn.readInt () & 0xffffffffL)
              + " secs since midnight 1/1/1904");
            System.out.println (sMarginIn + "Modification Time    = " 
              + ((long) disIn.readInt () & 0xffffffffL)
              + " secs since midnight 1/1/1904");
            System.out.println (sMarginIn + "Time Scale           = " + disIn.readInt ()
              + " time units / second");
            System.out.println (sMarginIn + "Duration             = " + disIn.readInt ()
              + " time units in movie");
            System.out.println (sMarginIn + "Preferred Rate       = " + disIn.readInt ());
            System.out.println (sMarginIn + "Preferred Volume     = " + disIn.readShort ());
            System.out.println (sMarginIn + "Reserved (10 bytes)  = " + disIn.readInt ());
            disIn.readInt ();
            disIn.readShort ();
            System.out.println (sMarginIn + "Matrix               = "
              + "\ta=" + disIn.readInt () 
              + "\tb=" + disIn.readInt () 
              + "\tu=" + disIn.readInt ());
            System.out.println (sMarginIn + "  to convert 1 coord   "
              + "\tc=" + disIn.readInt () 
              + "\td=" + disIn.readInt () 
              + "\tv=" + disIn.readInt ());
            System.out.println (sMarginIn + "  system to another.   "
              + "\tx=" + disIn.readInt () 
              + "\ty=" + disIn.readInt () 
              + "\tw=" + disIn.readInt ());
            System.out.println (sMarginIn + "Preview Time         = " + disIn.readInt ()
              + " (time at which preview begins)");
            System.out.println (sMarginIn + "Preview Duration     = " + disIn.readInt ()
              + " movie time units");
            System.out.println (sMarginIn + "Poster Time          = " + disIn.readInt ()
              + " movie time units");
            System.out.println (sMarginIn + "Selection Time       = " + disIn.readInt ()
              + " (start time of the current selection)");
            System.out.println (sMarginIn + "Selection Duration   = " + disIn.readInt ()
              + " movie time units");
            System.out.println (sMarginIn + "Current Time         = " + disIn.readInt ()
              + " (current time position in movie)");
            System.out.println (sMarginIn + "Next Track ID        = " + disIn.readInt ()
              + " (ID to give next track added to this movie)");
            System.out.println ("\n");
            }

          else if (sAtomID.equals ("tkhd"))
            {
            System.out.println (sMarginIn + sLine);
            System.out.println (sMarginIn + "Version(1) Flags(3)  = 0x" 
              + Integer.toString (disIn.readInt (), 16));
            System.out.println (sMarginIn + "Creation Time        = " 
              + ((long) disIn.readInt () & 0xffffffffL)
              + " secs since midnight 1/1/1904");
            System.out.println (sMarginIn + "Modification Time    = " 
              + ((long) disIn.readInt () & 0xffffffffL)
              + " secs since midnight 1/1/1904");
            System.out.println (sMarginIn + "Track ID             = " 
              + disIn.readInt ());
            System.out.println (sMarginIn + "Reserved (4 bytes)   = " 
              + disIn.readInt ());
            System.out.println (sMarginIn + "Duration             = " 
              + disIn.readInt ());
            System.out.println (sMarginIn + "Reserved (8 bytes)   = " 
              + disIn.readInt ());
            disIn.readInt ();
            System.out.println (sMarginIn + "Layer                = "
              + (disIn.readShort () & 0xffff));
            System.out.println (sMarginIn + "Alternate Group      = "
              + (disIn.readShort () & 0xffff));
            System.out.println (sMarginIn + "Volume               = "
              + (disIn.readShort () & 0xffff));
            System.out.println (sMarginIn + "Reserved (2 bytes)   = "
              + (disIn.readShort () & 0xffff));
            System.out.println (sMarginIn + "Matrix               = "
              + "\ta=" + disIn.readInt () 
              + "\tb=" + disIn.readInt () 
              + "\tu=" + disIn.readInt ());
            System.out.println (sMarginIn + "  to convert 1 coord   "
              + "\tc=" + disIn.readInt () 
              + "\td=" + disIn.readInt () 
              + "\tv=" + disIn.readInt ());
            System.out.println (sMarginIn + "  system to another.   "
              + "\tx=" + disIn.readInt () 
              + "\ty=" + disIn.readInt () 
              + "\tw=" + disIn.readInt ());
            System.out.println (sMarginIn + "Track Width          = " 
              + disIn.readInt ());
            System.out.println (sMarginIn + "Track Height         = " 
              + disIn.readInt ());
            System.out.println ("\n");
            }

          else if (sAtomID.equals ("stsz"))
            {
            System.out.println (sMarginIn + sLine);
            System.out.println (sMarginIn + "Version(1) Flags(3)  = 0x" 
              + Integer.toString (disIn.readInt (), 16));
            iSampleSize = disIn.readInt ();
            System.out.println (sMarginIn + "Sample Size          = " + iSampleSize);
            iSize = disIn.readInt ();
            System.out.println (sMarginIn + "Number of Entries    = " + iSize);
            if (iSampleSize == 0)
              {
              for (i = 0; i < iSize; i++)
                {
                s = "";
                if (i < 100) s += " ";
                if (i < 10) s += " ";
                System.out.println (sMarginIn + "          table[" + s + i 
                  + "] = " + disIn.readInt () + " bytes");
                }
              }
            System.out.println ("\n");
            }

          else if (sAtomID.equals ("stsd"))
            {
            System.out.println (sMarginIn + sLine);
            System.out.println (sMarginIn + "Version(1) Flags(3)  = 0x" 
              + Integer.toString (disIn.readInt (), 16) + "  ???");
            iSize = disIn.readInt ();
            System.out.println (sMarginIn + "Number of Entries    = " 
              + iSize + "    ???");
            for (i = 0; i < iSize; i++)
              {
              s1 = "";
              if (i < 100) s1 += " ";
              if (i < 10) s1 += " ";
              s1 = "[" + s1 + i + "] ";
              s = "      ";
              iSize2 = disIn.readInt ();
              System.out.println (sMarginIn + s1 + "Sample Desc Table Size     = "
                + iSize2 + " bytes");
              disIn.read (ab4);
              System.out.println (sMarginIn + s + "Encoding                   = "
                + new String (ab4));
              System.out.println (sMarginIn + s + "Reserved (6 bytes)         = "
                + disIn.readInt ());
              disIn.readShort ();
              System.out.println (sMarginIn + s + "Data Reference Index       = "
                + disIn.readShort () + " ('stsc' uses this)");
              System.out.println (sMarginIn + s + "Compr Data Version Number  = "
                + (disIn.readShort () & 0xffff));
              System.out.println (sMarginIn + s + "Compr Data Revision Level  = "
                + (disIn.readShort () & 0xffff));
              disIn.read (ab4);
              System.out.println (sMarginIn + s + "Vendor                     = "
                + new String (ab4));

              if (iSize2 == 36)       // audio structure 
                {
                System.out.println (sMarginIn + s + "Number of audio channels   = "
                  + (disIn.readShort () & 0xffff));
                System.out.println (sMarginIn + s + "Sample width in bits       = "
                  + (disIn.readShort () & 0xffff) + " bits");
                System.out.println (sMarginIn + s + "Compression ID             = "
                  + (disIn.readShort () & 0xffff));
                System.out.println (sMarginIn + s + "Packet Size                = "
                  + (disIn.readShort () & 0xffff));
                System.out.println (sMarginIn + s + "?? samplerate * 65536 ??   = "
                  + disIn.readInt ());
                }
              else if (iSize2 == 52)  // another audio structure 
                {
                System.out.println (sMarginIn + s + "Number of audio channels   = "
                  + (disIn.readShort () & 0xffff));
                System.out.println (sMarginIn + s + "Sample width in bits       = "
                  + (disIn.readShort () & 0xffff) + " bits");
                System.out.println (sMarginIn + s + "Compression ID             = "
                  + (disIn.readShort () & 0xffff));
                System.out.println (sMarginIn + s + "Packet Size                = "
                  + (disIn.readShort () & 0xffff));
                System.out.println (sMarginIn + s + "?? samplerate * 65536 ??   = "
                  + disIn.readInt ());
                System.out.println (sMarginIn + s + "?? 1                       = "
                  + disIn.readInt ());
                System.out.println (sMarginIn + s + "?? 2                       = "
                  + disIn.readInt ());
                System.out.println (sMarginIn + s + "?? 3                       = "
                  + disIn.readInt ());
                System.out.println (sMarginIn + s + "?? 4                       = "
                  + disIn.readInt ());
                }
              else                    // video structure
                {
                System.out.println (sMarginIn + s + "Temporal Quality           = "
                  + disIn.readInt ());
                System.out.println (sMarginIn + s + "Spatial Quality            = "
                  + disIn.readInt ());
                System.out.println (sMarginIn + s + "Image Width                = "
                  + (disIn.readShort () & 0xffff));
                System.out.println (sMarginIn + s + "Image Height               = "
                  + (disIn.readShort () & 0xffff));
                System.out.println (sMarginIn + s + "Horizontal Resolution      = "
                  + disIn.readInt () + " pixels/inch");
                System.out.println (sMarginIn + s + "Vertical Resolution        = "
                  + disIn.readInt () + " pixels/inch");
                System.out.println (sMarginIn + s + "Data Size                  = "
                  + disIn.readInt ());
                System.out.println (sMarginIn + s + "Num Frames / Sample        = "
                  + (disIn.readShort () & 0xffff));
                disIn.read (ab32);
                System.out.println (sMarginIn + s + "Compression Vendor Name    = "
                  + new String (ab32));
                System.out.println (sMarginIn + s + "Color Depth                = "
                  + (disIn.readShort () & 0xffff) + " bits/pixel");
                System.out.println (sMarginIn + s + "Color Table                = "
                  + (disIn.readShort () & 0xffff) + " (65535 means use default table)");
                }
              }
            System.out.println ("\n");
            }

          else if (sAtomID.equals ("stts"))
            {
            System.out.println (sMarginIn + sLine);
            System.out.println (sMarginIn + "Version(1) Flags(3)  = 0x" 
              + Integer.toString (disIn.readInt (), 16));
            iSize = disIn.readInt ();
            System.out.println (sMarginIn + "Number of Entries    = " + iSize);
            for (i = 0; i < iSize; i++)
              {
              s = "";
              if (i < 100) s += " ";
              if (i < 10) s += " ";
              System.out.println (sMarginIn + "          table[" + s + i 
                + "] = count=" + disIn.readInt () + " duration=" + disIn.readInt ()
                + " (movie time scale units)");
              }
            System.out.println ("\n");
            }

          else if (sAtomID.equals ("stss"))
            {
            System.out.println (sMarginIn + sLine);
            System.out.println (sMarginIn + "Version(1) Flags(3)  = 0x" 
              + Integer.toString (disIn.readInt (), 16));
            iSize = disIn.readInt ();
            System.out.println (sMarginIn + "Number of Entries    = " + iSize);
            for (i = 0; i < iSize; i++)
              {
              s = "";
              if (i < 100) s += " ";
              if (i < 10) s += " ";
              System.out.println (sMarginIn + "          table[" + s + i 
                + "] = sample " + disIn.readInt () + " is a key frame");
              }
            System.out.println ("\n");
            }

          else if (sAtomID.equals ("stsc"))
            {
            System.out.println (sMarginIn + sLine);
            System.out.println (sMarginIn + "Version(1) Flags(3)  = 0x" 
              + Integer.toString (disIn.readInt (), 16));
            iSize = disIn.readInt ();
            System.out.println (sMarginIn + "Number of Entries    = " + iSize);
            for (i = 0; i < iSize; i++)
              {
              s = "";
              if (i < 100) s += " ";
              if (i < 10) s += " ";
              System.out.println (sMarginIn + "          table[" + s + i 
                + "] = 1stChunkNumMatchingThis="  + disIn.readInt () 
                + " SamplesInChunk="              + disIn.readInt () 
                + " DescDataRefIndexInSTSD="      + disIn.readInt ());
              }
            System.out.println ("\n");
            }

          else if (sAtomID.equals ("stco"))
            {
            System.out.println (sMarginIn + sLine);
            System.out.println (sMarginIn + "Version(1) Flags(3)  = 0x" 
              + Integer.toString (disIn.readInt (), 16));
            iSize = disIn.readInt ();
            System.out.println (sMarginIn + "Number of Entries    = " + iSize);
            for (i = 0; i < iSize; i++)
              {
              s = "";
              if (i < 100) s += " ";
              if (i < 10) s += " ";
              System.out.println (sMarginIn + "          table[" + s + i 
                + "] = offset 0x"  + Integer.toString (disIn.readInt (), 16));
              }
            System.out.println ("\n");
            }

          else if (sAtomID.equals ("mdat"))
            {
            disIn.skip (iLength - 8);
            System.out.println ("\n\n");
            }

          else if (sAtomID.equals ("esds"))
            {
            System.out.println ();
            System.out.println (new Dump (disIn, iLength - 4, sMarginIn)); 
                  // 4 instead of 8 because the data is 4 bytes toolong
            }

          else
            {
            System.out.println ();
            System.out.println (new Dump (disIn, iLength - 8, sMarginIn));
            }
          }

        catch (Throwable t)
          {
          System.out.println ("dumpAtom() error: " + t);
          System.exit (1);
          }
        }

      return (iTotalLength);
      }



/*    D U M P   M O V I E
 */
/**   Dump the previously loaded Quicktime movie.
 *
 */
public void dumpMovie (String sReportTitle)
      {
      String sMargin;
      DataInputStream dis;
      ByteArrayInputStream bais;

      bais = new ByteArrayInputStream (mabBuffer);
      dis = new DataInputStream (bais);
      sMargin = "  ";
      System.out.println ("DUMP QUICKTIME MOVIE: " + sReportTitle);
      System.out.println ();

      dumpAtom (dis, mabBuffer.length, sMargin);
      }



/*    G E T   A T O M   D E S C R I P T I O N
 */
/**   Return a string that describes the passed integral atom id.
 *
 */
public static String getAtomDescription (int iAtomIDIn)
      {
      return (masAtomsAndDescriptions[iAtomIDIn].substring (8));
      }



/*    G E T   A T O M   F U L L   D E S C R I P T I O N
 */
/**   Return a string that includes the 4 character atom id with a
 *    description of the atom.
 *
 */
public static String getAtomFullDescription (int iAtomIDIn)
      {
      return (masAtomsAndDescriptions[iAtomIDIn]);
      }



/*    G E T   A T O M   I D 
 */
/**   Get an integer that represents the passed 4 character atom id.
 *
 */
public static int getAtomID (String sAtomIDIn)
      {
      int i;

      for (i = 0; i < masAtomsAndDescriptions.length; i++)
        {
        if (masAtomsAndDescriptions[i].startsWith (sAtomIDIn)) return (i);
        }

      return (0);
      }



/*    I S   C O N T A I N E R   A T O M
 */
/**   Returns true if the passed atom id is a container atom.
 *
 */
public static boolean isContainerAtom (String sAtomIDIn)
      {
      if (sAtomIDIn.equals ("clip") 
      ||  sAtomIDIn.equals ("dinf") 
      ||  sAtomIDIn.equals ("edts") 
      ||  sAtomIDIn.equals ("matt") 
      ||  sAtomIDIn.equals ("mdia") 
      ||  sAtomIDIn.equals ("minf") 
      ||  sAtomIDIn.equals ("moov")
      ||  sAtomIDIn.equals ("stbl") 
      ||  sAtomIDIn.equals ("trak") 
      ||  sAtomIDIn.equals ("udta"))
        {
        return (true);
        }

      return (false);
      }

      }


/*===========================(  End of Source  )===========================*/

