FileDocCategorySizeDatePackage
VideoSampleBuilder.javaAPI DocExample13698Wed Nov 10 12:37:18 GMT 2004com.oreilly.qtjnotebook.ch08

VideoSampleBuilder.java

/*

Copyright (c) 2004, Chris Adamson

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
package com.oreilly.qtjnotebook.ch08;

import quicktime.*;
import quicktime.io.*;
import quicktime.util.QTPointer;
import quicktime.qd.*;
import quicktime.std.*;
import quicktime.std.movies.*;
import quicktime.std.movies.media.*;
import quicktime.std.image.*;
import quicktime.util.*;

import com.oreilly.qtjnotebook.ch01.QTSessionCheck;

import java.io.*;
import java.util.Random;
import java.util.Properties;

public class VideoSampleBuilder extends Object {

    public static final int VIDEO_TRACK_WIDTH = 320;
    public static final int VIDEO_TRACK_HEIGHT = 240;
    public static final int VIDEO_TRACK_VOLUME = 0;
    public static final int KEY_FRAME_RATE = 30;

    Properties userProps = new Properties();
    QDRect startRect = null;
    QDRect endRect = null;

    public VideoSampleBuilder() throws QTException, IOException {

        /* try to load "videoSampleBuilder.properties" from 
           current directory.  this contains file.location and
           start.x/y/width/height and end.x/y/width/height params
         */
        try {
            userProps.load (new FileInputStream (new File ("videosamplebuilder.properties")));
            System.out.println ("Loaded \"videosamplebuilder.properties\"");
        } catch (Exception e) {
            System.out.println ("Couldn't load \"videosamplebuilder.properties");
        }

        int CODEC_TYPE = QTUtils.toOSType ("SVQ3");
        // int CODEC_TYPE = QTUtils.toOSType("mp4v");

        // create a new empty movie
        QTFile movFile = new QTFile (new java.io.File("videotrack.mov"));
        Movie movie =
            Movie.createMovieFile(movFile,
                                  StdQTConstants.kMoviePlayer,
                                  StdQTConstants.createMovieFileDeleteCurFile |
                                  StdQTConstants.createMovieFileDontCreateResFile);
        System.out.println ("Created Movie");

        // now create an empty video track
        int timeScale = 600; // 100 units per second
        Track videoTrack = movie.addTrack (VIDEO_TRACK_WIDTH,
                                           VIDEO_TRACK_HEIGHT,
                                           VIDEO_TRACK_VOLUME);
        System.out.println ("Added empty Track");

        // now we need media for this track
		VideoMedia videoMedia = new VideoMedia(videoTrack,
                                               timeScale);

        // get image file from props or dialog
        QTFile imgFile = getImageFile();
        if (imgFile == null)
            return;

        // get a GraphicsImporter
        GraphicsImporter importer = new GraphicsImporter (imgFile);
        System.out.println ("Got GraphicsImporter - Bounds are " +
                            importer.getNaturalBounds());

        // Create an offscreen QDGraphics / GWorld that's the
        // size of our frames.  Importer will draw into this,
        // and we'll then hand it to the CSequence
        QDGraphics gw =
            new QDGraphics (new QDRect (0, 0,
                                        VIDEO_TRACK_WIDTH,
                                        VIDEO_TRACK_HEIGHT));
        System.out.println ("Created GWorld, - Bounds are " +
                            gw.getBounds());

        // get start, end rects
        getRects (importer);
        System.out.println ("startRect = " + startRect);
        System.out.println ("endRect = " + endRect);

        // set importer's gworld
        importer.setGWorld (gw, null);
        System.out.println ("Reset importer's GWorld, now: " +
                            importer.getGWorld());
                            
        // get to work
        videoMedia.beginEdits();

        // figure out per-frame offsets
        QDRect gRect = new QDRect (0, 0,
                                      VIDEO_TRACK_WIDTH,
                                      VIDEO_TRACK_HEIGHT);
        int frames = 300;
        int startX = startRect.getX();
        int startY = startRect.getY();
        int endX = endRect.getX();
        int endY = endRect.getY();
        float xOffPerFrame = ((float)(endX - startX) / (float)frames);
        float yOffPerFrame = ((float)(endY - startY) / (float)frames);
        float widthOffPerFrame = ((float) (endRect.getWidth() -
                                           startRect.getWidth()) /
                                  (float) frames);
        float heightOffPerFrame = ((float) (endRect.getHeight() -
                                           startRect.getHeight()) /
                                  (float) frames);

        System.out.println ("xOffPerFrame=" + xOffPerFrame +
                            ", yOffPerFrame=" + yOffPerFrame +
                            ", widthOffPerFrame=" + widthOffPerFrame +
                            ", heightOffPerFrame=" + heightOffPerFrame);

        // reserve an image with enough space to hold compressed image
        // this is needed by the last arg of CSequence.compressFrame
		int rawImageSize =
            QTImage.getMaxCompressionSize (gw, 
                                           gRect, 
                                           gw.getPixMap().getPixelSize(),
                                           StdQTConstants.codecNormalQuality, 
                                           CODEC_TYPE,
                                           // CodecComponent.anyCodec);
                                           CodecComponent.bestFidelityCodec);
		QTHandle imageHandle = new QTHandle (rawImageSize, true);
		imageHandle.lock();
		RawEncodedImage compressedImage =
            RawEncodedImage.fromQTHandle(imageHandle);

        // create a CSequence
        /* see behavior flags at
http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESI/compresssequencebegin.htm#//apple_ref/c/func/CompressSequenceBegin
        */
		CSequence seq = new CSequence (gw,
                                       gRect, 
                                       gw.getPixMap().getPixelSize(),
                                       CODEC_TYPE,
                                       CodecComponent.bestFidelityCodec,
                                       StdQTConstants.codecNormalQuality, 
                                       StdQTConstants.codecNormalQuality, 
                                       KEY_FRAME_RATE,
                                       null,
                                       0);

        // remember an ImageDescription from this sequence definition
        ImageDescription imgDesc = seq.getDescription();

        // loop through the specified number of frames, drawing 
        // scaled instances into our GWorld and compressing those
        // to the CSequence
        for (int i=1; i<frames; i++) {
            System.out.println ("i==" + i);

            // compute a rect for this frame
            int x = startX + (int) (xOffPerFrame * i);
            int y = startY + (int) (yOffPerFrame * i);
            int width = startRect.getWidth() + (int) (widthOffPerFrame * i);
            int height = startRect.getHeight() + (int) (heightOffPerFrame * i);
            QDRect fromRect = new QDRect (x, y, width, height);

            // create a Matrix to represent the move/scale from
            // the fromRect to the GWorld and make importer use it
            Matrix drawMatrix = new Matrix();
            drawMatrix.rect (fromRect, gRect);
            System.out.println ("fromRect = " + fromRect);
            importer.setMatrix (drawMatrix);

            // have importer draw (scaled) into our GWorld
            importer.draw();
            System.out.println ("Importer drew");

            // compress a frame
            /* behavior flags at
http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESI/compresssequenceframe.htm#//apple_ref/c/func/CompressSequenceFrame
            */
			CompressedFrameInfo cfInfo =
                seq.compressFrame (gw, 
                                   gRect, 
                                   StdQTConstants.codecFlagUpdatePrevious, 
                                   compressedImage);
            System.out.println ("similarity = " + cfInfo.getSimilarity());

            // see http://developer.apple.com/qa/qtmcc/qtmcc20.html
            // for explanation of mediaSampleNotSync
            boolean syncSample = (cfInfo.getSimilarity() == 0);
            int flags = syncSample ? 0 : StdQTConstants.mediaSampleNotSync;

            // add compressed frame to the video media
            videoMedia.addSample (imageHandle, 
                                  0, 
                                  cfInfo.getDataSize(),
                                  20, // time per frame, in timescale
                                  imgDesc,
                                  1, // one sample
                                  flags
                                  );
        } // for

        // done editing
        videoMedia.endEdits();

        // now insert this media into track
        videoTrack.insertMedia (0, // trackStart
                                0, // mediaTime
                                videoMedia.getDuration(), // mediaDuration
                                1); // mediaRate
        System.out.println ("inserted media into video track");

        // save up 
        System.out.println ("Saving...");
        OpenMovieFile omf = OpenMovieFile.asWrite (movFile);
        movie.addResource (omf,
                           StdQTConstants.movieInDataForkResID,
                           movFile.getName());
        System.out.println ("Done");

    }

    /** Gets imageFile from props file, or file-preview if 
        that doesn't work.
     */
    protected QTFile getImageFile () throws QTException {
        // is it in the props?
        QTFile imageFile = null;
        if (userProps.containsKey ("file")) {
            imageFile = new QTFile (userProps.getProperty("file"));
            if (! imageFile.exists())
                imageFile = null;
        }

        // if not, or if that failed, then use a dialog
        if (imageFile == null) {
            int[] types = {};
            imageFile = QTFile.standardGetFilePreview (types);
        }
        return imageFile;
    }

    /** Gets startRect, endRect from userProps, or selects
        randomly if that doesn't work
     */
    protected void getRects (GraphicsImporter importer) throws QTException {
        Random rand = new Random();
        int rightStop =
            importer.getNaturalBounds().getWidth() - VIDEO_TRACK_WIDTH;
        int bottomStop =
            importer.getNaturalBounds().getHeight() - VIDEO_TRACK_HEIGHT;

        // try to get startRect from userProps
        try {
            int startX = Integer.parseInt (userProps.getProperty("start.x"));
            int startY = Integer.parseInt (userProps.getProperty("start.y"));
            int startWidth = Integer.parseInt (userProps.getProperty("start.width"));
            int startHeight = Integer.parseInt (userProps.getProperty("start.height"));
            startRect = new QDRect (startX, startY, startWidth, startHeight);
        } catch (Exception e) {
            // make random start rect
            int startX = Math.abs (rand.nextInt() % rightStop);
            int startY = Math.abs (rand.nextInt() % bottomStop);
            startRect = new QDRect (startX, startY, 
                                    VIDEO_TRACK_WIDTH,
                                    VIDEO_TRACK_HEIGHT);
        }

        // try to get endRect from userProps
        try {
            int endX = Integer.parseInt (userProps.getProperty("end.x"));
            int endY = Integer.parseInt (userProps.getProperty("end.y"));
            int endWidth = Integer.parseInt (userProps.getProperty("end.width"));
            int endHeight = Integer.parseInt (userProps.getProperty("end.height"));
            endRect = new QDRect (endX, endY, endWidth, endHeight);

        } catch (Exception e) {
            float zoom = (rand.nextFloat() - 0.5f); // -0.5 <= zoom <= 0.5
            System.out.println ("zoom = " + zoom);
            int endX = Math.abs (rand.nextInt() % rightStop);
            int endY = Math.abs (rand.nextInt() % bottomStop);
            endRect = new QDRect (endX, endY,
                                  VIDEO_TRACK_WIDTH * zoom,
                                  VIDEO_TRACK_HEIGHT * zoom);
        }
    }


    public static void main (String[] arrrImAPirate) {
        try {
            QTSessionCheck.check();
            new VideoSampleBuilder();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.exit(0);
    }

}