src/CreateMovie.java

/*
 * QuickTime for Java SDK Sample Code
 
   Usage subject to restrictions in SDK License Agreement
 * Copyright: © 1996-1999 Apple Computer, Inc.
 
 */
 
import java.awt.*;
import java.awt.event.*;
import java.io.*;
 
import quicktime.qd.*;
import quicktime.*;
import quicktime.std.*;
import quicktime.io.*;
import quicktime.sound.*;
import quicktime.std.image.*;
import quicktime.std.movies.*;
import quicktime.std.movies.media.*;
import quicktime.util.*;
 
import createmovies.*;
 
import quicktime.app.display.*;
import quicktime.app.image.*;
import quicktime.app.QTFactory;
/**
 *  CreateMovie Demo.
 */
 
public class CreateMovie extends Frame implements StdQTConstants, Errors {
    static CreateMovie movieFrame;
 
//_________________________ CLASS METHODS
    public static void main(String args[]) {
        try {
            QTSession.open();
 
            movieFrame = new CreateMovie("Movie Creation Demo using QuickTime and Java");
            movieFrame.addWindowListener (new WindowAdapter () {
                public void windowOpened (WindowEvent we) {
                    movieFrame.makeMovie();
                }
                public void windowClosing (WindowEvent e) {
                    movieFrame.canv.removeClient();
                    QTSession.close();
                    movieFrame.dispose();
                }
                public void windowClosed (WindowEvent e) { 
                    System.exit(0);
                }
            });
            movieFrame.show();
            movieFrame.toFront();
        } catch (Exception e) {
            e.printStackTrace();
            QTSession.close();
        }
    }
 
    public CreateMovie (String frameTitle) throws Exception {
        super(frameTitle);
        soundFile = QTFactory.findAbsolutePath ("sound.aif");
 
        canv = new QTCanvas (QTCanvas.kInitialSize, 0.5F, 0.5F);
        add ("Center", canv);
 
        np = new NumberPainter(numFrames);
        qid = new QTImageDrawer (np, new Dimension (kWidth, kHeight), Redrawable.kMultiFrame);
        qid.setRedrawing(true);
 
        canv.setClient (qid, true);
    
        pack();
    }       
 
//_________________________ INSTANCE VARIABLES
    private QTCanvas canv;
    private QTImageDrawer qid;
    private NumberPainter np;
    private static final int numFrames = 30;
    private int kWidth = 330;
    private int kHeight = 140;
    private File soundFile;
    
//_________________________ INSTANCE METHODS
    private void makeMovie() {
        try {       
            //
            // show save-as dialog, create movie file & empty movie
            //
            FileDialog fd = new FileDialog (this, "Save Movie As...", FileDialog.SAVE);
            fd.show();
            if(fd.getFile() == null)
                throw new QTIOException (userCanceledErr, "");
            QTFile f = new QTFile(fd.getDirectory() + fd.getFile());
            Movie theMovie = Movie.createMovieFile (f,
                                kMoviePlayer, 
                                createMovieFileDeleteCurFile | createMovieFileDontCreateResFile);
 
            //
            // add content
            //
            System.out.println ("Doing Video Track");
            addVideoTrack( theMovie );
            System.out.println ("Doing Audio Track");
            addAudioTrack( theMovie );
 
            //
            // save movie to file
            //
            OpenMovieFile outStream = OpenMovieFile.asWrite (f); 
            theMovie.addResource( outStream, movieInDataForkResID, f.getName() );
            outStream.close();
            System.out.println ("Finished movie");
        }
        catch (Exception qte) {
            qte.printStackTrace(); 
        }
    }
    
    private void addVideoTrack (Movie theMovie) throws QTException {
        int kNoVolume   = 0;
        int kVidTimeScale = 600;
 
        Track vidTrack = theMovie.addTrack (kWidth, kHeight, kNoVolume);
        VideoMedia vidMedia = new VideoMedia (vidTrack, kVidTimeScale);  
                                
        vidMedia.beginEdits();
        addVideoSample (vidMedia);
        vidMedia.endEdits();
        
        int kTrackStart = 0;
        int kMediaTime  = 0;
        int kMediaRate  = 1;
        vidTrack.insertMedia (kTrackStart, kMediaTime, vidMedia.getDuration(), kMediaRate);
    }
    
    private void addVideoSample( VideoMedia vidMedia ) throws QTException {
        QDRect rect = new QDRect (kWidth, kHeight);
        QDGraphics gw = new QDGraphics (rect);
        int size = QTImage.getMaxCompressionSize (gw, 
                                                rect, 
                                                gw.getPixMap().getPixelSize(),
                                                codecNormalQuality, 
                                                kAnimationCodecType, 
                                                CodecComponent.anyCodec);
        QTHandle imageHandle = new QTHandle (size, true);
        imageHandle.lock();
        RawEncodedImage compressedImage = RawEncodedImage.fromQTHandle(imageHandle);
        CSequence seq = new CSequence (gw,
                                        rect, 
                                        gw.getPixMap().getPixelSize(),
                                        kAnimationCodecType, 
                                        CodecComponent.bestFidelityCodec,
                                        codecNormalQuality, 
                                        codecNormalQuality, 
                                        numFrames,  //1 key frame
                                        null, //cTab,
                                        0);
        ImageDescription desc = seq.getDescription();
 
 //redraw first...
        np.setCurrentFrame (1);
        qid.redraw(null);
 
        qid.setGWorld (gw);
        qid.setDisplayBounds (rect);
            
        for (int curSample = 1; curSample <= numFrames; curSample++) {
            np.setCurrentFrame (curSample);
            qid.redraw(null);
            CompressedFrameInfo info = seq.compressFrame (gw, 
                                                        rect, 
                                                        codecFlagUpdatePrevious, 
                                                        compressedImage);
            boolean isKeyFrame = info.getSimilarity() == 0;
            System.out.println ("f#:" + curSample + ",kf=" + isKeyFrame + ",sim=" + info.getSimilarity());
            vidMedia.addSample (imageHandle, 
                                    0, // dataOffset,
                                    info.getDataSize(),
                                    60, // frameDuration, 60/600 = 1/10 of a second, desired time per frame 
                                    desc,
                                    1, // one sample
                                    (isKeyFrame ? 0 : mediaSampleNotSync)); // no flags
       }
        
    //print out ImageDescription for the last video media data ->
    //this has a sample count of 1 because we add each "frame" as an individual media sample
        System.out.println (desc);
 
    //redraw after finishing...
        qid.setGWorld (canv.getPort());
        np.setCurrentFrame (numFrames);
        qid.redraw(null);
    }
    
    static class SoundData {
        QTHandle sampleData;
        SoundDescription description;
        int numSamples;
    }
    
    private void addAudioTrack (Movie theMovie) throws QTException, IOException {
        int kFullVolume = 1;
            
            // fills in the above SoundData class fields
        SoundData theSound = getSound();
    
            //print out the SoundDescription
        System.out.println (theSound.description);
            
            //create Sound track with SoundMedia object -> the rounded sample rate is the time scale of the sound media
        Track sndTrack = theMovie.addTrack (0, 0, kFullVolume);
        SoundMedia sndMedia = new SoundMedia (sndTrack, theSound.description.getSampleRateRounded());  
 
            //add the sample data into the sound media
        sndMedia.beginEdits();
        sndMedia.addSample (theSound.sampleData,
                                0,
                                theSound.sampleData.getSize(),
                                1, // duration of each sound sample,
                                theSound.description,
                                theSound.numSamples,
                                0);
        sndMedia.endEdits();
            
            // insert the media into the track
        int kTrackStart = 0;
        int kMediaTime  = 0;
        int kMediaRate  = 1;
        sndTrack.insertMedia (kTrackStart, kMediaTime, sndMedia.getDuration(), kMediaRate);
    }
    
    private SoundData getSound() throws QTException, IOException {      
        //
        // read sound.aif file into memory and create a description and read the sound data out of it.
        //
                                                
        SndInfo info = SndInfo.parseAIFFHeader (OpenFile.asRead (new QTFile(soundFile)));
        
        SoundData sd = new SoundData();
 
        SoundComponentData mySndInfo = info.sndData;
 
        sd.description = new SoundDescription (mySndInfo.getFormat());
        sd.description.setNumberOfChannels (mySndInfo.getNumChannels());
        sd.description.setSampleSize (mySndInfo.getSampleSize());
        sd.description.setSampleRate (mySndInfo.getSampleRate());
        
        int mySampleDataSize = mySndInfo.getSampleCount() * (mySndInfo.getSampleSize() / 8); 
            //read just the sample data into memory
            // it is sample data size and is found at the data offset location in the file
            // as returned by the ParseAIFF header call
            FileInputStream fis = new FileInputStream (soundFile);
            byte[] ar = new byte [mySampleDataSize];            
            fis.skip (info.dataOffset);
            fis.read (ar, 0, mySampleDataSize);
        
        sd.sampleData = new QTHandle (ar);
        
        sd.numSamples = mySndInfo.getSampleCount();
        return sd;
    }
}
 
 
/* THIS way captures the result of the draw method of the QTImageDrawer
    it then adds the raw pixel data to the movie, getting that raw data and description
    from the ImagePresenter of the QTImageDrawer
    
    the way we add data above is to compress the GWorld the QTImageDrawer draws to
    using the animation compressor -> which also gives us frame differencing and a smalller data size
    and better playback for the movie.
    
    this code is presented as an alternative which may be appropriate in certain instances
    -> like adding image data for a sprite where no fidelity on each image is lost.
    in this case the important point is NOT the QTImageDrawer but the addition
    of the raw EncodedImage and ImageDescription -> how we get that is up to the application.
    
        for (int curSample = 1; curSample <= numFrames; curSample++) {
            QTUtils.reclaimMemory();
            np.setCurrentFrame (curSample);
            qid.redraw(null);
            ImagePresenter ip = qid.toImagePresenter();
            ImageDescription desc = ip.getDescription();
            EncodedImage imageData = ip.getImage(1);            
            vidMedia.addSample (QTHandle.fromEncodedImage(imageData), 
                                    0, // dataOffset,
                                    desc.getDataSize(),
                                    60, // frameDuration, 60/600 = 1/10 of a second, desired time per frame 
                                    desc,
                                    1, // one sample
                                    0 ); // no flags
        }
*/