mirror of
https://github.com/processing/processing4.git
synced 2026-02-04 06:09:17 +01:00
adding shiffman's moviemaker
This commit is contained in:
292
video/src/processing/video/MovieMaker.java
Normal file
292
video/src/processing/video/MovieMaker.java
Normal file
@@ -0,0 +1,292 @@
|
||||
/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2006 Daniel Shiffman
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General
|
||||
Public License along with this library; if not, write to the
|
||||
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
||||
Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
package processing.video;
|
||||
|
||||
import quicktime.Errors;
|
||||
import quicktime.QTException;
|
||||
import quicktime.QTSession;
|
||||
import quicktime.io.OpenMovieFile;
|
||||
import quicktime.io.QTFile;
|
||||
import quicktime.qd.QDConstants;
|
||||
import quicktime.qd.QDGraphics;
|
||||
import quicktime.qd.QDRect;
|
||||
import quicktime.std.StdQTConstants;
|
||||
import quicktime.std.StdQTException;
|
||||
import quicktime.std.image.CSequence;
|
||||
import quicktime.std.image.CodecComponent;
|
||||
import quicktime.std.image.CompressedFrameInfo;
|
||||
import quicktime.std.image.ImageDescription;
|
||||
import quicktime.std.image.QTImage;
|
||||
import quicktime.std.movies.Movie;
|
||||
import quicktime.std.movies.Track;
|
||||
import quicktime.std.movies.media.VideoMedia;
|
||||
import quicktime.util.QTHandle;
|
||||
import quicktime.util.RawEncodedImage;
|
||||
|
||||
import processing.core.*;
|
||||
|
||||
|
||||
/**
|
||||
* Library to create a QuickTime movie from a Processing pixel array.
|
||||
* Written by <A HREF="http://www.shiffman.net">Daniel Shiffman</A>.
|
||||
* Thanks to Dan O'Sullivan and Shawn Van Every.
|
||||
*/
|
||||
public class MovieMaker {
|
||||
|
||||
public static final int RAW = StdQTConstants.kRawCodecType;
|
||||
public static final int JPEG = StdQTConstants.kJPEGCodecType;
|
||||
public static final int CINEPAK = StdQTConstants.kCinepakCodecType;
|
||||
public static final int SORENSON = StdQTConstants.kSorensonCodecType;
|
||||
public static final int VIDEO = StdQTConstants.kVideoCodecType;
|
||||
public static final int H261 = StdQTConstants.kH261CodecType;
|
||||
public static final int H263 = StdQTConstants.kH263CodecType;
|
||||
public static final int BASE = StdQTConstants.kBaseCodecType;
|
||||
public static final int BMP = StdQTConstants.kBMPCodecType;
|
||||
public static final int CMYK = StdQTConstants.kCMYKCodecType;
|
||||
public static final int COMPONENT = StdQTConstants.kComponentVideoCodecType;
|
||||
public static final int GIF = StdQTConstants.kGIFCodecType;
|
||||
public static final int GRAPHICS = StdQTConstants.kGraphicsCodecType;
|
||||
public static final int MSVideo = StdQTConstants.kMicrosoftVideo1CodecType;
|
||||
public static final int MOTIONJPEGA = StdQTConstants.kMotionJPEGACodecType;
|
||||
public static final int MOTIONJPEGB = StdQTConstants.kMotionJPEGBCodecType;
|
||||
|
||||
public static final int WORST = StdQTConstants.codecMinQuality;
|
||||
public static final int LOW = StdQTConstants.codecLowQuality;
|
||||
public static final int MEDIUM = StdQTConstants.codecNormalQuality;
|
||||
public static final int HIGH = StdQTConstants.codecHighQuality;
|
||||
public static final int BEST = StdQTConstants.codecMaxQuality;
|
||||
public static final int LOSSLESS = StdQTConstants.codecLosslessQuality;
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
private boolean readyForFrames;
|
||||
|
||||
private int TIME_SCALE = 1000;
|
||||
|
||||
// QT Stuff
|
||||
private VideoMedia videoMedia;
|
||||
private Track videoTrack;
|
||||
private Movie movie;
|
||||
private QTFile movFile;
|
||||
private CSequence seq;
|
||||
private QTHandle imageHandle;
|
||||
private QDGraphics gw;
|
||||
private QDRect bounds;
|
||||
private ImageDescription imgDesc;
|
||||
private RawEncodedImage compressedImage;
|
||||
|
||||
private int rate;
|
||||
private int keyFrameRate = 15;
|
||||
private int codecType, codecQuality;
|
||||
|
||||
// my hack to make sure we don't get error -8691
|
||||
private boolean temporalSupported = true;
|
||||
|
||||
private PApplet parent;
|
||||
|
||||
|
||||
public MovieMaker(PApplet p, int _w, int _h, String _filename) {
|
||||
this(p, _w, _h, _filename, 0, 0, 30, 15);
|
||||
}
|
||||
|
||||
|
||||
public MovieMaker(PApplet p, int _w, int _h, String _filename,
|
||||
int _codecType, int _codecQuality) {
|
||||
this(p, _w, _h, _filename, _codecType, _codecQuality, 30, 15);
|
||||
}
|
||||
|
||||
|
||||
public MovieMaker(PApplet p, int _w, int _h, String _filename,
|
||||
int _codecType, int _codecQuality, int _rate) {
|
||||
this(p, _w, _h, _filename, _codecType, _codecQuality, _rate, 15);
|
||||
}
|
||||
|
||||
public MovieMaker(PApplet p, int _w, int _h, String _filename,
|
||||
int _codecType, int _codecQuality, int _rate,
|
||||
int _keyFrameRate) {
|
||||
parent = p;
|
||||
|
||||
width = _w;
|
||||
height = _h;
|
||||
rate = _rate;
|
||||
|
||||
// Start QT
|
||||
//if (!QTSession.isInitialized()) { // [fry]
|
||||
try {
|
||||
QTSession.open();
|
||||
} catch (QTException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
//}
|
||||
|
||||
// Create GWorld
|
||||
try {
|
||||
// Broken on intel?
|
||||
// ImageDescription imgD =
|
||||
// new ImageDescription(QDConstants.k32ARGBPixelFormat);
|
||||
ImageDescription imgD = null;
|
||||
// Intel fix, could it be?? I think this takes care of Windows too?!?!?
|
||||
if (quicktime.util.EndianOrder.isNativeLittleEndian()) {
|
||||
imgD = new ImageDescription(QDConstants.k32BGRAPixelFormat);
|
||||
} else {
|
||||
imgD = new ImageDescription(QDGraphics.kDefaultPixelFormat);
|
||||
}
|
||||
imgD.setWidth(width);
|
||||
imgD.setHeight(height);
|
||||
gw = new QDGraphics(imgD, 0);
|
||||
|
||||
} catch (QTException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
codecType = _codecType;
|
||||
codecQuality = _codecQuality;
|
||||
keyFrameRate = _keyFrameRate;
|
||||
initMovie(_filename);
|
||||
|
||||
parent.registerDispose(this);
|
||||
}
|
||||
|
||||
|
||||
private void initMovie(String filename) {
|
||||
// If nothing has been specified
|
||||
if (codecType == 0) {
|
||||
System.out.println("No code type specified, using default of RAW");
|
||||
codecType = RAW;
|
||||
}
|
||||
if (codecQuality == 0) {
|
||||
System.out.println("No code quality specified, using default of HIGH");
|
||||
codecQuality = HIGH;
|
||||
}
|
||||
|
||||
try {
|
||||
String path = parent.savePath(filename);
|
||||
System.out.println("Creating movie file: " + path);
|
||||
movFile = new QTFile (new java.io.File(path));
|
||||
movie = Movie.createMovieFile(movFile, StdQTConstants.kMoviePlayer, StdQTConstants.createMovieFileDeleteCurFile);
|
||||
int timeScale = TIME_SCALE; // 100 units per second
|
||||
videoTrack = movie.addTrack(width, height, 0);
|
||||
videoMedia = new VideoMedia(videoTrack, timeScale);
|
||||
videoMedia.beginEdits();
|
||||
bounds = new QDRect(0, 0, width, height);
|
||||
int rawImageSize = QTImage.getMaxCompressionSize(gw, bounds, gw.getPixMap().getPixelSize(), codecQuality, codecType, CodecComponent.anyCodec);
|
||||
imageHandle = new QTHandle(rawImageSize, true);
|
||||
imageHandle.lock();
|
||||
compressedImage = RawEncodedImage.fromQTHandle(imageHandle);
|
||||
seq = new CSequence(gw, bounds, gw.getPixMap().getPixelSize(), codecType, CodecComponent.bestFidelityCodec, codecQuality, codecQuality, keyFrameRate, null, 0);
|
||||
imgDesc = seq.getDescription();
|
||||
readyForFrames = true;
|
||||
|
||||
} catch (QTException e) {
|
||||
// if it's a -8961 error, do it the other way
|
||||
if (e.errorCode() == Errors.noCodecErr) {
|
||||
//System.out.println("Temporal compression not supported, switching modes. ");
|
||||
temporalSupported = false;
|
||||
readyForFrames = true;
|
||||
|
||||
} else if (e.errorCode() == Errors.fBsyErr) {
|
||||
System.err.println("Movie file already exists. " +
|
||||
"Please delete it first. " +
|
||||
"(DS to solve this eventually.)");
|
||||
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void finishMovie() {
|
||||
System.out.println("Finishing movie file.");
|
||||
try {
|
||||
readyForFrames = false;
|
||||
videoMedia.endEdits();
|
||||
videoTrack.insertMedia(0, 0, videoMedia.getDuration(), 1);
|
||||
OpenMovieFile omf = OpenMovieFile.asWrite(movFile);
|
||||
movie.addResource(omf, StdQTConstants.movieInDataForkResID,
|
||||
movFile.getName());
|
||||
} catch (StdQTException e) {
|
||||
e.printStackTrace();
|
||||
} catch (QTException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// Causes windows to hang??
|
||||
//QTSession.close();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
if (readyForFrames) finishMovie();
|
||||
|
||||
try {
|
||||
QTSession.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void addFrame(int[] _pixels, int w, int h) {
|
||||
// Now that I fixed the intel mac bug in the constructor, I think windows
|
||||
// is covered too so the code below is unnecessary. I think. I hope.
|
||||
//boolean windows = false;
|
||||
//String os = System.getProperty("os.name");
|
||||
//if (os.charAt(0) == 'w' || os.charAt(0) == 'W') windows = true;
|
||||
if (readyForFrames){
|
||||
RawEncodedImage pixelData = gw.getPixMap().getPixelData();
|
||||
int rowBytes = pixelData.getRowBytes() / 4;
|
||||
int[] newpixels = new int[rowBytes*h];
|
||||
for (int i = 0; i < rowBytes; i++) {
|
||||
for (int j = 0; j < h; j++) {
|
||||
if (i < w) {
|
||||
newpixels[i+j*rowBytes] = _pixels[i+j*w];
|
||||
// We can skip this now, right?!?!?!?
|
||||
// On windows, we have to flip the pixels (i think)
|
||||
//if (!windows) newpixels[i+j*rowBytes] = _pixels[i+j*w];
|
||||
//else newpixels[i+j*rowBytes] = EndianOrder.flipBigEndianToNative32(_pixels[i+j*w]);
|
||||
}
|
||||
else newpixels[i+j*rowBytes] = 0;
|
||||
}
|
||||
}
|
||||
pixelData.setInts(0,newpixels);
|
||||
compressAndAdd();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void compressAndAdd() {
|
||||
try {
|
||||
if (temporalSupported) {
|
||||
CompressedFrameInfo cfInfo = seq.compressFrame(gw, bounds, StdQTConstants.codecFlagUpdatePrevious, compressedImage);
|
||||
boolean syncSample = cfInfo.getSimilarity() == 0; // see developer.apple.com/qa/qtmcc/qtmcc20.html
|
||||
videoMedia.addSample(imageHandle, 0, cfInfo.getDataSize(), TIME_SCALE/rate, imgDesc, 1, syncSample ? 0 : StdQTConstants.mediaSampleNotSync);
|
||||
} else {
|
||||
imgDesc = QTImage.fCompress(gw,gw.getBounds(),32,codecQuality,codecType, CodecComponent.anyCodec, null, 0, RawEncodedImage.fromQTHandle(imageHandle));
|
||||
boolean syncSample = true; // UM, what the hell should this be???
|
||||
videoMedia.addSample(imageHandle, 0, imgDesc.getDataSize(), TIME_SCALE/rate, imgDesc, 1, syncSample ? 0 : StdQTConstants.mediaSampleNotSync);
|
||||
}
|
||||
} catch (QTException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user