package util;
//----- JDK Imports ------------------------------------------------------------
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.util.Vector;
import javax.swing.JDialog;
import javax.swing.JPanel;
//----- Quicktime Imports ------------------------------------------------------
import quicktime.QTException;
import quicktime.io.IOConstants;
import quicktime.io.OpenMovieFile;
import quicktime.io.QTFile;
import quicktime.qd.Pict;
import quicktime.qd.QDColor;
import quicktime.qd.QDConstants;
import quicktime.qd.QDDimension;
import quicktime.qd.QDFont;
import quicktime.qd.QDGraphics;
import quicktime.qd.QDRect;
import quicktime.std.StdQTConstants;
import quicktime.std.StdQTException;
import quicktime.std.clocks.TimeRecord;
import quicktime.std.image.CSequence;
import quicktime.std.image.CodecComponent;
import quicktime.std.image.CompressedFrameInfo;
import quicktime.std.image.GraphicsImporter;
import quicktime.std.image.GraphicsMode;
import quicktime.std.image.ImageDescription;
import quicktime.std.image.Matrix;
import quicktime.std.image.QTImage;
import quicktime.std.movies.Movie;
import quicktime.std.movies.TimeInfo;
import quicktime.std.movies.Track;
import quicktime.std.movies.media.DataRef;
import quicktime.std.movies.media.Media;
import quicktime.std.movies.media.SoundMedia;
import quicktime.std.movies.media.TextMedia;
import quicktime.std.movies.media.TextMediaHandler;
import quicktime.std.movies.media.TimeCodeMedia;
import quicktime.std.movies.media.VideoMedia;
import quicktime.std.movies.media.VisualMediaHandler;
import quicktime.std.qtcomponents.TCTextOptions;
import quicktime.std.qtcomponents.TimeCodeDef;
import quicktime.std.qtcomponents.TimeCodeDescription;
import quicktime.std.qtcomponents.TimeCodeTime;
import quicktime.std.qtcomponents.TimeCoder;
import quicktime.util.EndianOrder;
import quicktime.util.QTHandle;
import quicktime.util.QTPointer;
import quicktime.util.QTUtils;
import quicktime.util.RawEncodedImage;
//----- SISC Imports -----------------------------------------------------------
import sisc.data.Pair;
import sisc.interpreter.SchemeException;
//----- Phoenix Imports --------------------------------------------------------
import controller.PhoenixController;
/**
* Video Phoenix
* Version 0.2.0
* Copyright (c) 2007 Lunderskov, Ian; Pan, Jiabei; Rebelsky, Samuel;
* Whisenhunt, Heather; Young, Ian; Zuleta Benavides, Luis.
* All rights reserved.
* 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.
* @author Lunderskov, Ian; Pan, Jiabei; Rebelsky, Samuel; Whisenhunt, Heather;
* Young, Ian; Zuleta Benavides, Luis
* @author Glimmer Labs 2006-2007
* @version 0.2.0
*/
public class MovieUtils
{
/*---------*------------------------------------------------------------------
* Methods *
*---------*/
/**
* Create a copy of a movie
*
* @param mov Movie to copy
* @return copy Copy of mov
*/
public static Movie cloneMovie(Movie mov)
{
Movie movie = null;
try
{
movie = new Movie();
mov.insertSegment(movie, 0, mov.getDuration(), 0);
movie.setDefaultDataRef(new DataRef(new QTHandle()));
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return movie;
} // cloneMovie(Movie)
/**
* Add an audio track to a movie
*
* @param source Audio track to add
* @param target Movie to add audio track to
* @return track Track in target containing contents of source
* @throws QTException
*/
public static Track addAudioTrack(Track source, Movie target)
throws QTException
{
Track targetTrack = target.newTrack(0f, 0f, 20);
@SuppressWarnings("unused")
SoundMedia media = new SoundMedia(targetTrack,
source.getMedia().getTimeScale(), new DataRef(new QTHandle()));
source.insertSegment(targetTrack, 0, source.getDuration(), 0);
return targetTrack;
} // addAudioTrack(Track, Movie)
/**
* Add a text track to a movie
*
* @param mov Movie to add text track to
* @param text Text to be added
* @param fontName Font name
* @param fontOptions Font style (e.g. "bold", "italic", "underline", ...)
* @param fontSize Font size
* @param fgColor RGB values for text color (e.g. [0,0,0] for black)
* @param bgColor RGB values for box color (e.g. [0,0,0] for black)
* @param x X-coordinate on mov of upper left corner of text box
* @param y Y-coordinate on mov of upper left corner of text box
* @param start Time in mov to begin displaying text track
* @param duration Duration in seconds to display text track
* @return result Movie with text track
*/
// Adapted from
// Chris Adamson, QuickTime for Java: A Developer's Notebook
// Published by O'Reilly, 2005
// p. 190
public static Movie addTextTrack(Movie mov, String text, String fontName,
String fontOptions, int fontSize, int[] fgColor, int[] bgColor, int x,
int y, float start, float duration)
{
// make a copy of the original movie
Movie movie = MovieUtils.cloneMovie(mov);
try
{
movie = new Movie();
mov.insertSegment(movie, 0, mov.getDuration(), 0);
movie.setDefaultDataRef(new DataRef(new QTHandle()));
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
// determine opaque / transparent background color; transparency is
// indicated by a -1 in the first cell of bgColor
int dfFlag = 0;
if (bgColor[0] == -1)
{
dfFlag = StdQTConstants.dfAntiAlias | StdQTConstants.dfKeyedText;
} // if (bgColor[0] == -1)
else
{
dfFlag = StdQTConstants.dfAntiAlias;
} // else
// set up the Java font to estimate dimensions of the actual text
// track using QDFont
Font font = null;
int style = 0;
if (fontOptions.equalsIgnoreCase("plain"))
{
style = QDConstants.normal;
font = new Font(fontName, Font.PLAIN, fontSize);
} // if (fontOptions.equalsIgnoreCase("plain"))
else if (fontOptions.equalsIgnoreCase("bold"))
{
style = QDConstants.bold;
font = new Font(fontName, Font.BOLD, fontSize);
} // else if (fontOptions.equalsIgnoreCase("bold"))
else if (fontOptions.equalsIgnoreCase("italic"))
{
style = QDConstants.italic;
font = new Font(fontName, Font.ITALIC, fontSize + 2);
} // else if (fontOptions.equalsIgnoreCase("italic"))
else if (fontOptions.equalsIgnoreCase("bolditalic"))
{
style = QDConstants.bold | QDConstants.italic;
font = new Font(fontName, Font.PLAIN + Font.ITALIC, fontSize + 5);
} // else if (fontOptions.equalsIgnoreCase("bolditalic"))
else if (fontOptions.equalsIgnoreCase("underline"))
{
style = QDConstants.underlined;
font = new Font(fontName, Font.ITALIC + Font.BOLD, fontSize);
} // else if (fontOptions.equalsIgnoreCase("underline"))
else if (fontOptions.equalsIgnoreCase("boldunderline"))
{
style = QDConstants.underlined | QDConstants.bold;
font = new Font(fontName, Font.ITALIC + Font.BOLD, fontSize + 1);
} // else if (fontOptions.equalsIgnoreCase("boldunderline"))
else if (fontOptions.equalsIgnoreCase("italicunderline"))
{
style = QDConstants.underlined | QDConstants.italic;
font = new Font(fontName, Font.ITALIC + Font.BOLD, fontSize + 1);
} // else if (fontOptions.equalsIgnoreCase("italicunderline"))
else if (fontOptions.equalsIgnoreCase("bolditalicunderline"))
{
style = QDConstants.underlined| QDConstants.italic | QDConstants.bold;
font = new Font(fontName, Font.ITALIC + Font.BOLD, fontSize + 1);
} // else if (fontOptions.equalsIgnoreCase("bolditalicunderline"))
JDialog tempF = new JDialog();
JPanel temp = new JPanel();
temp.setFont(font);
tempF.add(temp);
// must be visible, otherwise g2 is null
tempF.setVisible(true);
temp.setVisible(true);
Graphics2D g2 = (Graphics2D)temp.getGraphics();
g2.setFont(font);
g2.drawString(text, 0, 0);
tempF.setVisible(false);
FontRenderContext context = g2.getFontRenderContext();
Rectangle2D rect = font.getStringBounds(text, context);
int TEXT_TRACK_WIDTH = (int)rect.getWidth();
int TEXT_TRACK_HEIGHT = (int)rect.getHeight();
temp = null;
tempF = null;
QDRect textBox = new QDRect(0, 0, TEXT_TRACK_WIDTH, TEXT_TRACK_HEIGHT);
// add text track
Track textTrack;
try
{
textTrack = movie.addTrack(TEXT_TRACK_WIDTH, TEXT_TRACK_HEIGHT, 0);
Media textMedia = new TextMedia(textTrack, movie.getTimeScale());
TextMediaHandler handler = (TextMediaHandler)textMedia.getHandler();
textMedia.beginEdits();
byte[] msgBytes = text.getBytes();
QTPointer msgPoint = new QTPointer(msgBytes);
handler.addTextSample(msgPoint, QDFont.getFNum(fontName), fontSize, style,
new QDColor(fgColor[0] / 255f, fgColor[1] / 255f, fgColor[2] / 255f),
new QDColor(bgColor[0] / 255f, bgColor[1] / 255f, bgColor[2] / 255f),
QDConstants.teJustLeft, textBox, dfFlag, QDFont.getFNum(fontName), 0, 0,
QDColor.white, Math.round(duration * movie.getTimeScale()));
textMedia.endEdits();
textTrack.insertMedia(Math.round(start * movie.getTimeScale()), 0,
textMedia.getDuration(), 1);
// position the text track to (x, y)
Matrix mat = new Matrix();
mat.rect(new QDRect(0, 0, TEXT_TRACK_WIDTH, TEXT_TRACK_HEIGHT),
new QDRect(x, y, TEXT_TRACK_WIDTH, TEXT_TRACK_HEIGHT));
textTrack.setMatrix(mat);
} // try
catch (Exception e)
{
e.printStackTrace();
} // catch (Exception)
return movie;
} // addTextTrack(Movie, String, String, String, int, int[], int[], int, ...
/**
* Add a video track to a movie
*
* @param source Video track to add
* @param target Movie to add video track to
* @return track Track in target containing contents of source
* @throws QTException
*/
public static Track addVideoTrack(Track source, Movie target)
throws QTException
{
Track targetTrack = target.newTrack(source.getSize().getWidthF(),
source.getSize().getHeightF(), 1.0f);
@SuppressWarnings("unused")
VideoMedia media = new VideoMedia(targetTrack,
source.getMedia().getTimeScale(), new DataRef(new QTHandle()));
int duration = source.getDuration();
source.insertSegment(targetTrack, 0, duration, 0);
return targetTrack;
} // addVideoTrack(Track, Movie)
/**
* Add a time code track to a movie
*
* @param mov Movie to add time code track to
* @param fontName Font name
* @param fontOptions Font style (e.g. "bold", "bolditalic", "italic", ...)
* @param fontSize Font size
* @param fgColor RGB values for text color (e.g. [0,0,0] for black)
* @param bgColor RGB values for box color (e.g. [0,0,0] for black)
* @param x X-coordinate on mov of upper left corner of text box
* @param y Y-coordinate on mov of upper left corner of text box
* @param width Width of text box in pixels
* @param height Height of text box in pixels
* @return result Movie with time code track
* @throws QTException
*/
// Adapted from
// Chris Adamson, QuickTime for Java: A Developer's Notebook
// Published by O'Reilly, 2005
// p. 197
public static Movie addTimeCode(Movie mov, String fontName,
String fontOptions, int fontSize, int[] fgColor, int[] bgColor, int x,
int y, int width, int height)
throws QTException
{
// make a new copy of mov
Movie movie = cloneMovie(mov);
int timescale = movie.getTimeScale();
TimeCodeDef tcDef = new TimeCodeDef();
tcDef.setTimeScale(movie.getTimeScale());
int fps = MovieUtils.countFrames(movie, 0, 1);
tcDef.setFrameDuration(movie.getTimeScale() / fps);
tcDef.setFramesPerSecond(fps);
tcDef.setFlags(StdQTConstants.tcDropFrame);
// first record at 0 hrs, 0 min, 0 sec, 0 frames
TimeCodeTime tcTime = new TimeCodeTime (0, 0, 0, 0);
// create time code track and media
Track tcTrack = movie.addTrack (width, height, 0);
TimeCodeMedia tcMedia = new TimeCodeMedia (tcTrack, timescale);
TimeCoder timeCoder = tcMedia.getTimeCodeHandler();
int style = 0;
if (fontOptions.equalsIgnoreCase("plain"))
{
style = QDConstants.normal;
} // if (fontOptions.equalsIgnoreCase("plain"))
else if (fontOptions.equalsIgnoreCase("bold"))
{
style = QDConstants.bold;
} // else if (fontOptions.equalsIgnoreCase("bold"))
else if (fontOptions.equalsIgnoreCase("italic"))
{
style = QDConstants.italic;
} // else if (fontOptions.equalsIgnoreCase("italic"))
else if (fontOptions.equalsIgnoreCase("bolditalic"))
{
style = QDConstants.bold | QDConstants.italic;
} // else if (fontOptions.equalsIgnoreCase("bolditalic"))
else if (fontOptions.equalsIgnoreCase("underline"))
{
style = QDConstants.underlined;
} // else if (fontOptions.equalsIgnoreCase("underline"))
else if (fontOptions.equalsIgnoreCase("boldunderline"))
{
style = QDConstants.underlined | QDConstants.bold;
} // else if (fontOptions.equalsIgnoreCase("boldunderline"))
else if (fontOptions.equalsIgnoreCase("italicunderline"))
{
style = QDConstants.underlined | QDConstants.italic;
} // else if (fontOptions.equalsIgnoreCase("italicunderline"))
else if (fontOptions.equalsIgnoreCase("bolditalicunderline"))
{
style = QDConstants.underlined| QDConstants.italic | QDConstants.bold;
} // else if (fontOptions.equalsIgnoreCase("bolditalicunderline"))
// turn on time code display, set colors
timeCoder.setFlags(timeCoder.getFlags() | StdQTConstants.tcdfShowTimeCode,
StdQTConstants.tcdfShowTimeCode);
TCTextOptions tcTextOptions = timeCoder.getDisplayOptions();
tcTextOptions.setTXSize(fontSize);
tcTextOptions.setTXFace(style);
tcTextOptions.setTXFont(QDFont.getFNum(fontName));
tcTextOptions.setForeColor(new QDColor(fgColor[0] / 255f, fgColor[1] / 255f,
fgColor[2] / 255f));
// if transparency is indicated, set background color to a special value
if (bgColor[0] == -1)
{
tcTextOptions.setBackColor(new QDColor(0.1f, 0.7f, 0.43f));
} // if (bgColor[0] == -1)
else
{
tcTextOptions.setBackColor(new QDColor(bgColor[0] / 255f,
bgColor[1] / 255f, bgColor[2] / 255f));
} // else
timeCoder.setDisplayOptions(tcTextOptions);
// set up a sample as a 4-byte array in a QTHandle
int frameNumber = timeCoder.toFrameNumber(tcTime, tcDef);
int frameNums[] = new int[1];
// BOOK ERRATA: this is buggy on Windows for time codes other
// than 00:00:00;00. You need to adjust for endianness, as
// seen in the revised (uncommented) line.
// frameNums[0] = frameNumber;
frameNums[0] = EndianOrder.flipNativeToBigEndian32(frameNumber);
QTHandle frameNumHandle = new QTHandle (4, false);
frameNumHandle.copyFromArray(0, frameNums, 0, 1);
// create a time code description (sample to be added)
TimeCodeDescription tcDesc = new TimeCodeDescription();
tcDesc.setTimeCodeDef (tcDef);
// add the sample to the TimeCodeMedia
tcMedia.beginEdits();
tcMedia.addSample(frameNumHandle, 0, frameNumHandle.getSize(),
movie.getDuration(), tcDesc, 1, 0);
tcMedia.endEdits();
// insert media into track
tcTrack.insertMedia(0, 0, tcMedia.getDuration(), 1);
// set a transparent-background GrahpicsMode
QDRect moveFrom = new QDRect(0, 0, width, height);
QDRect moveTo = new QDRect(x, y, width, height);
Matrix matrix = new Matrix();
matrix.rect(moveFrom, moveTo);
tcTrack.setMatrix (matrix);
// if transparency is indicated, make the special value the transparent
// color
if (bgColor[0] == -1)
{
timeCoder.setGraphicsMode(new GraphicsMode(QDConstants.transparent,
new QDColor(0.1f, 0.7f, 0.43f)));
} // if (bgColor[0] == -1)
return movie;
} // addTimeCode(Movie, String, String, int, int[], int[], int, int, int, int)
/**
* Append two movies
*
* @param mov1 Movie to append
* @param mov2 Movie to append
* @return result mov2 appended to end of mov1
*/
public static Movie movieAppend(Movie mov1, Movie mov2)
{
Movie composite = null;
Movie movie1 = null;
Movie movie2 = null;
try
{
QDRect box1 = mov1.getBox();
QDRect box2 = mov2.getBox();
int height1 = box1.getHeight();
int height2 = box2.getHeight();
int width1 = box1.getWidth();
int width2 = box2.getWidth();
// variables for the matrix translations
float a = 0, b = 0, c = 0, d = 0;
movie1 = new Movie();
movie2 = new Movie();
composite = new Movie();
// insert the movies to new movie files, in order to resize without
// affecting originals
mov1.insertSegment(movie1, 0, mov1.getDuration(), 0);
mov2.insertSegment(movie2, 0, mov2.getDuration(), 0);
Track track1 = movie1.getIndTrackType(1,
StdQTConstants.videoMediaType,
StdQTConstants.movieTrackMediaType);
Track track2 = movie2.getIndTrackType(1,
StdQTConstants.videoMediaType,
StdQTConstants.movieTrackMediaType);
Matrix matrix1 = track1.getMatrix();
Matrix matrix2 = track2.getMatrix();
// translate tracks based on size
if (box1.equals(box2))
{
// do nothing - no resizing needed
} // if (box1.equals(box2))
else if ((height1 >= height2) && (width1 >= width2))
{
c = (width1 - width2) / 2;
d = (height1 - height2) / 2;
} // else if ((height1 >= height2) && (width1 >= width2))
else if ((height2 >= height1) && (width2 >= width1))
{
a = (width2 - width1) / 2;
b = (height2 - height1) / 2;
} // else if ((height2 >= height1) && (width2 >= width1))
else if ((height1 >= height2) && (width2 >= width1))
{
a = (width2 - width1) / 2;
d = (height1 - height2) / 2;
} // else if ((height1 >= height2) && (width2 >= width1))
else if ((height2 >= height1) && (width1 >= width2))
{
b = (height2 - height1) / 2;
c = (width1 - width2) / 2;
} // else if ((height2 >= height1) && (width1 >= width2))
// modify matrices based on changes made in the if-clause above
matrix1.translate(a, b);
matrix2.translate(c, d);
// set tracks to changed matrices
track1.setMatrix(matrix1);
track2.setMatrix(matrix2);
// add the two movies with translated video tracks to the returned
// composite
movie2.insertSegment(composite, 0, movie2.getDuration(), 0);
movie1.insertSegment(composite, 0, movie1.getDuration(), 0);
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return composite;
} // movieAppend(Movie, Movie)
/**
* Extract a segment of movie
*
* @param mov Movie to extract segment from
* @param start Time in mov to start extracting
* @param end Time in mov to stop extracting
* @return segment Segment of mov from start to end
*/
public static Movie extractSegment(Movie mov, int start, int end)
{
return MovieUtils.extractSegment(mov, (float) start, (float) end);
} // extractSegment(Movie, int, int)
/**
* Extract a segment of a movie
*
* @param mov Movie to extract segment from
* @param start Time in mov to start extracting
* @param end Time in mov to stop extracting
* @return segment Segment of mov from start to end
*/
public static Movie extractSegment(Movie mov, float start, float end)
{
Movie section = null;
try
{
section = new Movie();
int begin = Math.round(start * mov.getTimeScale());
int finish = Math.round(end * mov.getTimeScale());
mov.insertSegment(section, begin, finish - begin, 0);
section.setTimeScale(mov.getTimeScale());
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return section;
} // extractSegment(Movie, float, float)
/**
* Insert a movie into another movie
*
* @param src Movie to insert into dest
* @param dest Movie to insert src into
* @param time Time in dest to insert src
* @return result dest with src inserted at time
*/
public static Movie insertSegment(Movie src, Movie dest, float time)
{
Movie union = null;
try
{
// create a new movie
union = new Movie();
// convert time from seconds to a time in dest's time scale
int there = Math.round(time * dest.getTimeScale());
// copy dest into new movie up until the time of insertion
dest.insertSegment(union, 0, there, 0);
// copy src, in its entirety, into new movie
src.insertSegment(union, 0, src.getDuration(), union.getDuration());
// copy rest of dest into new movie
dest.insertSegment(union, there, dest.getDuration(), union.getDuration());
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return union;
} // insertSegment(Movie, Movie, float)
/**
* Open a file chooser to save a movie
* Types of saved movies:
* Movie - a QuickTime reference movie that contains only references to
* media in their original locations
* Movie, self-contained - a QuickTime movie with its own copy of all media
* Movie to hinted movie - a self-contained QuickTime movie but allowing
* user to adjust hinting settings for use in a streaming server
* Movie to QT movie - a self-contained QuickTime movie but allowing user to
* choose different compressors and settings to re-encode audio and video
*
* @param mov Movie to save
*/
public static void saveMovie(Movie mov)
{
try
{
QTFile file = new QTFile(new File("mymovie.mov"));
int flags = StdQTConstants.createMovieFileDeleteCurFile
| StdQTConstants.createMovieFileDontCreateResFile
| StdQTConstants.showUserSettingsDialog;
mov.setProgressProc();
mov.convertToFile(file, StdQTConstants.kQTFileTypeMovie,
StdQTConstants.kMoviePlayer, IOConstants.smSystemScript, flags);
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
} // saveMovie(Movie)
/**
* Resize a movie
*
* @param mov Movie to be resized
* @param aspect Whether aspect ratio should be held
* @param width Width in pixels to resize mov to
* @param height Height in pixels to resize mov to (ignored if aspect is
* true)
* @return result Resized mov
*/
public static Movie resize(Movie mov, boolean aspect, int width, int height)
{
Movie movie = null;
try
{
// create a new copy of the movie
movie = cloneMovie(mov);
Track video = movie.getIndTrackType(1,
StdQTConstants.videoMediaType,
StdQTConstants.movieTrackMediaType);
QDDimension dim_size = video.getSize();
// aspect ratio before resizing.
float originalaspect = (dim_size.getWidthF() / dim_size.getHeightF());
// aspect with new dimensions
float changedaspect = ((float) width / (float) height);
// scaling factor to adjust aspect ratios
float scale = changedaspect * (1 / originalaspect);
// get track matrix to preserve other scaling changes
Matrix stretch = video.getMatrix();
QDRect oldsize = new QDRect(0, 0, dim_size.getWidthF(),
dim_size.getHeightF());
QDRect newsize;
// determine whether the aspect ratio needs to be checked/maintained
if (aspect)
{
if (originalaspect != changedaspect)
{
height = (int) (height * scale);
} // if (originalaspect != changedaspect)
} // if (aspect)
newsize = new QDRect(0, 0, width, height);
stretch.map(oldsize, newsize);
video.setMatrix(stretch);
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return movie;
} // resize(Movie, boolean, int, int)
/**
* Replace every pixel of a key color in a movie with a corresponding pixel
* from another movie
*
* @param movFore Foreground movie
* @param movBack Background movie
* @param red Red value of key color
* @param green Green value of key color
* @param blue Blue value of key color
* @return result movFore with pixels of the key color replaced with movBack
*/
// Adapted from
// Chris Adamson, QuickTime for Java: A Developer's Notebook
// Published by O'Reilly, 2005
// pp. 166~171
public static Movie chromaKey(Movie movFore, Movie movBack, int red,
int green, int blue)
{
Movie result = null;
QDColor key = new QDColor((float)red / 255, (float)green / 255,
(float)blue / 255);
try
{
result = new Movie();
// add front track
Track trackFore = movFore.getIndTrackType(1,
StdQTConstants.visualMediaCharacteristic,
StdQTConstants.movieTrackCharacteristic);
trackFore = MovieUtils.addVideoTrack(trackFore, result);
// add back track
Track trackBack = movBack.getIndTrackType(1,
StdQTConstants.visualMediaCharacteristic,
StdQTConstants.movieTrackCharacteristic);
trackBack = MovieUtils.addVideoTrack(trackBack, result);
// make sure both tracks have their actual size
Matrix matrix1 = new Matrix();
QDRect from1 = new QDRect(0, 0, trackFore.getSize().getWidth(),
trackFore.getSize().getHeight());
QDRect to1 = new QDRect(0, 0,
movFore.getDisplayBoundsRgn().getBounds().getWidth(),
movFore.getDisplayBoundsRgn().getBounds().getHeight());
matrix1.map(from1, to1);
Matrix matrix2 = new Matrix();
QDRect from2 = new QDRect(0, 0, trackBack.getSize().getWidth(),
trackBack.getSize().getHeight());
QDRect to2 = new QDRect(0, 0,
movBack.getDisplayBoundsRgn().getBounds().getWidth(),
movBack.getDisplayBoundsRgn().getBounds().getHeight());
matrix2.map(from2, to2);
GraphicsMode graph = new GraphicsMode(QDConstants.transparent, key);
// make pixels with key color transparent, so back movie shows through
VisualMediaHandler handlerFore =
(VisualMediaHandler)trackFore.getMedia().getHandler();
handlerFore.setGraphicsMode(graph);
trackBack.setMatrix(matrix2);
trackFore.setMatrix(matrix1);
trackFore.setLayer(-1);
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return result;
} // chromaKey(Movie, Movie, int, int, int)
/**
* Get a Pict from a movie
*
* @param mov Movie to get Pict from
* @param time Time in seconds in mov to get Pict
* @return pic Frame in mov at time
*/
public static Pict getMovieFrame(Movie mov, float time)
{
try
{
float oldRate = mov.getRate();
mov.stop();
time = time * (float) mov.getTimeScale();
Pict pic = mov.getPict((int) time);
mov.setRate(oldRate);
return pic;
} // try
catch (Exception e)
{
e.printStackTrace();
return null;
} // catch (Exception)
} // getMovieFrame(Movie, float)
/**
* Get the number of interesting frames in a section of a movie
*
* @param mov Movie to count frames in
* @param start Time in mov to start counting frames
* @param end Time in mov to stop counting frames
* @return frames Number of frames in mov from start to end
* @throws QTException
*/
public static int countFrames(Movie movie, float start, float end)
throws QTException
{
Movie mov = MovieUtils.cloneMovie(movie);
int timeScale = mov.getTimeScale();
int begin = (int)Math.floor(start * timeScale);
int finish = (int)Math.floor(end * timeScale);
int count = 0;
// get the video track from mov
Track visualTrack = mov.getIndTrackType(1,
StdQTConstants.visualMediaCharacteristic,
StdQTConstants.movieTrackCharacteristic);
mov.setTime(new TimeRecord(timeScale, (int) begin));
Boolean go = true;
int lastTime = 0;
do
{
TimeInfo ti = visualTrack.getNextInterestingTime(
StdQTConstants.nextTimeMediaSample, mov.getTime(), 1);
// if looped to earlier frame or finished selected section
if ((lastTime > ti.time) || (ti.time >= finish))
{
go = false;
} // if ((lastTime > ti.time) || (ti.time >= finish))
else
{
lastTime = ti.time;
mov.setTime(new TimeRecord(mov.getTimeScale(), ti.time));
++count;
} // else
} // do
while (go);
return count;
} // countFrames(Movie, float, float)
/**
* Get all frames in a selection of a movie
*
* @param mov Movie to get frames from
* @param start Time in mov to start getting frames
* @param end Time in mov to stop getting frames
* @return frames All frames in mov from start to end
* @throws SchemeException
*/
public static Pict[] getAllFrames(Movie mov, float start, float end)
throws SchemeException
{
Pict[] frames;
Vector<Pict> pics;
frames = new Pict[0];
pics = new Vector<Pict>();
try
{
Pict frame;
int timeScale = mov.getTimeScale();
int begin = (int)Math.floor(start * timeScale);
int finish = (int)Math.floor(end * timeScale);
// get the video track from mov
Track visualTrack = mov.getIndTrackType(1,
StdQTConstants.visualMediaCharacteristic,
StdQTConstants.movieTrackCharacteristic);
mov.setTime(new TimeRecord(timeScale, (int)begin));
Boolean go = true;
int lastTime = 0;
do
{
if (Thread.interrupted())
{
throw new SchemeException(new Pair(), null, null);
} // if (Thread.interrupted())
TimeInfo ti = visualTrack.getNextInterestingTime(
StdQTConstants.nextTimeMediaSample, mov.getTime(), 1);
// if looped to earlier frame or finished selected section
if ((lastTime > ti.time) || (ti.time >= finish))
{
go = false;
} // if ((lastTime > ti.time) || (ti.time >= finish))
else
{
// set time to next available frame and get Pict from that time
mov.setTime(new TimeRecord(mov.getTimeScale(), ti.time));
frame = mov.getPict(mov.getTime());
pics.add(frame);
} // else
} // do
while (go);
} // try
// catch two exceptions separately in order to pass SchemeException
// to interpreter
catch (StdQTException stdqte)
{
stdqte.printStackTrace();
} // catch (StdQTException)
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return pics.toArray(frames);
} // getAllFrames(Movie, float, float)
/**
* Compile an array of Picts into a movie with only a visual track using SVQ3
* codec
*
* @param pics Pics to build into movie
* @param frameRate Frame rate of movie
* @param timeScale QuickTime time scale of movie
* @return mov Movie with only a visual track containing contents of
* pics in order
*/
// Adapted from
// Chris Adamson, QuickTime for Java: A Developer's Notebook
// Published by O'Reilly, 2005
// p. 175
public static Movie compileFrames(Pict[] pics, int frameRate, int timeScale)
{
// codec to be used for compression
int CODEC_TYPE = QTUtils.toOSType("SVQ3");
// width and height of movie will be width and height of first pict in pics
float WIDTH = pics[0].getPictFrame().getWidthF();
float HEIGHT = pics[0].getPictFrame().getHeightF();
// video tracks don't need volume
int VOLUME = 0;
// make every frame a key (self-contained) frame
int KEY_FRAME_RATE = frameRate;
Movie newmovie = null;
java.util.Random rand = new java.util.Random();
try
{
int random = rand.nextInt();
// create a new empty movie and a track
String tempFile = "Phoenix" + java.lang.Integer.toString(random) + ".mov";
QTFile movFile = new QTFile(new File(tempFile));
newmovie = Movie.createMovieFile(movFile, StdQTConstants.kMoviePlayer,
StdQTConstants.createMovieFileDeleteCurFile
| StdQTConstants.createMovieFileDontCreateResFile);
Track videoTrack = newmovie.addTrack(WIDTH, HEIGHT, VOLUME);
// create media for new track
VideoMedia videoMedia = new VideoMedia(videoTrack, timeScale);
// get a GraphicsImporter
GraphicsImporter gi = new GraphicsImporter(
StdQTConstants.kQTFileTypePicture);
// create an offscreen QDGraphics / GWorld the size of the picts
// importer will draw into this and pass to CSequence
QDGraphics gw = new QDGraphics(new QDRect(0, 0, WIDTH, HEIGHT));
// set importer's GWorld
gi.setGWorld(gw, null);
QDRect gRect = new QDRect(0, 0, WIDTH, HEIGHT);
// add images to media
videoMedia.beginEdits();
int frames = pics.length;
int rawImageSize = QTImage.getMaxCompressionSize(gw, gRect,
gw.getPixMap().getPixelSize(), StdQTConstants.codecLosslessQuality,
CODEC_TYPE, CodecComponent.bestFidelityCodec);
QTHandle imageHandle = new QTHandle(rawImageSize, true);
imageHandle.lock();
RawEncodedImage compressed = RawEncodedImage.fromQTHandle(imageHandle);
CSequence seq = new CSequence(gw, gRect, gw.getPixMap().getPixelSize(),
CODEC_TYPE, CodecComponent.bestFidelityCodec,
StdQTConstants.codecLosslessQuality,
StdQTConstants.codecLosslessQuality,
KEY_FRAME_RATE, null, StdQTConstants.codecFlagUpdatePrevious);
ImageDescription imgDesc = seq.getDescription();
// attempt to loop through all frames, scaling to fit the first frame
for (int x = 0; x < frames; x++)
{
QDRect srcRect = new QDRect(0, 0, pics[x].getPictFrame().getWidthF(),
pics[x].getPictFrame().getHeightF());
// add 512 byte header so the grapics importer will think that it's
// dealing with a pict file
byte[] newPictBytes = new byte[pics[x].getSize() + 512];
pics[x].copyToArray(0, newPictBytes, 512, newPictBytes.length - 512);
pics[x] = new Pict(newPictBytes);
// export the pict
DataRef ref = new DataRef(pics[x],
StdQTConstants.kDataRefQTFileTypeTag, "PICT");
gi.setDataReference(ref);
// create matrix to represent scaling of each pict
Matrix drawMatrix = new Matrix();
drawMatrix.rect(srcRect, gRect);
gi.setMatrix(drawMatrix);
gi.draw();
// compress frame
CompressedFrameInfo cfInfo = seq.compressFrame(gw, gRect,
StdQTConstants.codecFlagUpdatePrevious, compressed);
// check to see if frame is a key frame
boolean syncSample = (cfInfo.getSimilarity() == 0);
int flags = syncSample ? 0 : StdQTConstants.mediaSampleNotSync;
// add compressed frame to video media
videoMedia.addSample(imageHandle, 0, cfInfo.getDataSize(),
timeScale / frameRate, imgDesc, 1, flags);
} // for
// done adding samples to the media
videoMedia.endEdits();
// insert media into track
videoTrack.insertMedia(0, 0, videoMedia.getDuration(), 1);
// save changes made into file and add to movie
OpenMovieFile omf = OpenMovieFile.asWrite(movFile);
newmovie.addResource(omf, StdQTConstants.movieInDataForkResID,
movFile.getName());
// delete file created; prevent any thread problems by terminating
// current thread
try
{
Thread.sleep(1000);
} // try
catch (InterruptedException ie)
{
// do nothing
} // catch (InterruptedException)
movFile.deleteOnExit();
omf.getFile().deleteOnExit();
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return newmovie;
} // compileFrames(Pict[], int, int)
/**
* Save a movie in a .pict file
*
* @param pic Pict to save
* @param path Path where pict will be saved
* @return succeeds Whether the save operation succeeded
*/
public static boolean saveFrame(Pict pic, String path)
{
boolean saved = false;
try
{
QTFile file = new QTFile(path);
pic.writeToFile(file);
saved = true;
} // try
catch (QTException qte)
{
PhoenixController.getSchemeController().printStyledText(
"\n IOError: Could not save the file, check your pathname and that the "
+ "file type is supported by Quicktime", "scheme_error");
} // catch (QTException)
catch (IOException ioe)
{
PhoenixController.getSchemeController().printStyledText(
"\n IOError: Could not save the file, check your pathname and that the"
+ " file type is supported by Quicktime", "scheme_error");
} // catch (IOException)
return saved;
} // saveFrame(Pict, String)
/**
* Get the first audio track of a movie
*
* @param mov Movie to get audio track from
* @return audio Audio track in mov
*/
public static Track getAudioTrack(Movie mov)
{
Track audio = null;
try
{
Movie movie = cloneMovie(mov);
audio = movie.getIndTrackType(1, StdQTConstants.soundMediaType,
StdQTConstants.movieTrackMediaType);
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return audio;
} // getAudioTrack(Movie)
/**
* Extract a segment of the first audio track in a movie
*
* @param mov Movie to get audio track from
* @param start Time in mov to start getting audio track
* @param end Time in mov to stop getting audio track
* @return audio Segment of audio track in mov from start to end
*/
public static Track extractAudioSegment(Movie mov, float start, float end)
{
Movie mseg = extractSegment(mov, start, end);
return getAudioTrack(mseg);
} // extractAudioSegment(Movie, float, float)
/**
* Add an audio track to a movie
*
* @param mov Movie to add audio to
* @param audio Audio track to add to movie
* @param start Time in movie to start adding audio
* @return result mov with audio added at start
*/
public static Movie addAudioTrack(Movie mov, Track audio, float start)
{
Movie newMov = null;
try
{
// make a copy of mov
newMov = cloneMovie(mov);
Track newAudio;
// use addEmptyTrack to make sure it has SoundMedia characteristics
newAudio = newMov.addEmptyTrack(audio, new DataRef(new QTHandle()));
audio.insertSegment(newAudio, 0, audio.getDuration(),
(int)(start * newMov.getTimeScale()));
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return newMov;
} // addAudioTrack(Movie, Track, float)
/**
* Add an audio track to a movie
*
* @param mov Movie to add audio to
* @param audio Audio track to add to mov
* @param start Time in mov to start adding audio
* @param duration Length of audio to add to mov
* @return result mov with aduio added at start
*/
public static Movie addAudioTrack(Movie mov, Track audio, float start,
float duration)
{
Movie newMov = null;
try
{
// make a copy of mov
newMov = cloneMovie(mov);
Track newAudio;
newAudio = newMov.addEmptyTrack(audio, new DataRef(new QTHandle()));
// use addEmptyTrack to make sure it has Sound Media characteristics
audio.insertSegment(newAudio, 0, (int) (duration
* audio.getMovie().getTimeScale()),
(int) (start * newMov.getTimeScale()));
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return newMov;
} // addAudioTrack(Movie, Track, float, float)
/**
* Remove the first audio track from a movie
*
* @param mov Movie to remove an audio track from
* @return result mov without the first audio track
*/
public static Movie removeAudioTrack(Movie mov)
{
Movie newMov = null;
try
{
// make a copy of mov
newMov = cloneMovie(mov);
// remove first track with SoundMedia characteristics
newMov.removeTrack(newMov.getIndTrackType(
1, StdQTConstants.soundMediaType, StdQTConstants.movieTrackMediaType));
} // try
catch (QTException qte)
{
qte.printStackTrace();
} // catch (QTException)
return newMov;
} // removeAudioTrack(Movie)
} // class MovieUtils