remove quicktime video library

This commit is contained in:
benfry
2011-06-18 16:24:09 +00:00
parent b85b05b56a
commit 4dac2e0a71
30 changed files with 0 additions and 3265 deletions

View File

@@ -1,549 +0,0 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2004-09 Ben Fry and Casey Reas
The previous version of this code was developed by Hernando Barragan
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, or (at your option) any later version.
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 processing.core.*;
import java.lang.reflect.*;
import quicktime.*;
import quicktime.qd.*;
import quicktime.std.*;
import quicktime.std.sg.*;
import quicktime.util.RawEncodedImage;
/**
* Watchin' shit on the telly.
*/
@SuppressWarnings("deprecation")
public class Capture extends PImage implements Runnable {
// there are more, but these are all we'll provide for now
// The useful ref page for <a href="http://developer.apple.com/documentation/Java/Reference/1.4.1/Java141API_QTJ/constant-values.html">quicktime constants</a>
static public final int COMPOSITE = StdQTConstants.compositeIn; // 0
static public final int SVIDEO = StdQTConstants.sVideoIn; // 1
static public final int COMPONENT = StdQTConstants.rgbComponentIn; // 2
static public final int TUNER = StdQTConstants.tvTunerIn; // 6
static public final int NTSC = StdQTConstants.ntscIn;
static public final int PAL = StdQTConstants.palIn;
static public final int SECAM = StdQTConstants.secamIn;
// no longer needed because parent field added to PImage
//PApplet parent;
Method captureEventMethod;
String name; // keep track for error messages (unused)
Thread runner;
boolean available = false;
/** Temporary storage for the raw image
data read directly from the capture device */
public int data[];
public int dataWidth;
public int dataHeight;
public int dataRowBytes;
/** True if this image is currently being cropped */
public boolean crop;
public int cropX;
public int cropY;
public int cropW;
public int cropH;
public int frameRate;
public RawEncodedImage raw;
public SequenceGrabber capture;
/** the guy who's doing all the work */
public SGVideoChannel channel;
/** boundary of image at the requested size */
protected QDRect qdrect;
/*
static {
try {
QTSession.open();
} catch (QTException e) {
e.printStackTrace();
}
// this doesn't appear to do jack
QTRuntimeException.registerHandler(new QTRuntimeHandler() {
public void exceptionOccurred(QTRuntimeException e,
Object obj, String s, boolean flag) {
System.err.println("Problem inside Capture");
e.printStackTrace();
}
});
}
*/
public Capture(PApplet parent, int requestWidth, int requestHeight) {
this(parent, requestWidth, requestHeight, null, 30);
}
public Capture(PApplet parent, int reqWidth, int reqHeight, int frameRate) {
this(parent, reqWidth, reqHeight, null, frameRate);
}
public Capture(PApplet parent, int reqWidth, int reqHeight, String name) {
this(parent, reqWidth, reqHeight, name, 30);
}
/**
* If 'name' is null or the empty string, it won't set a specific
* device, which means that QuickTime will use that last device
* used by a QuickTime application.
* <P/>
* Unfortunately, Apple's QuickTime API uses the name to select devices,
* and in some cases there might be cameras with the same name on a machine.
* If you ask for a camera of the same name in sequence, you might see if it
* just does the right thing and grabs each separate camera in succession.
* If that doesn't work, you might try calling settings() which will
* bring up the prompt where you can select a capture device.
* <P/>
* If the following function:
* <PRE>public void captureEvent(Capture c)</PRE>
* is defined in the host PApplet, then it will be called every
* time a new frame is available from the capture device.
*/
public Capture(final PApplet parent,
final int requestWidth, final int requestHeight,
final String name, final int frameRate) {
// Running on EDT because of weird hang on OS X
// http://dev.processing.org/bugs/show_bug.cgi?id=882
// QTSession.open() is hanging, not sure why, but it seems to prefer
// being run from the EDT. Not sure if that's a mistaken expectation in
// QTJava (we hadn't had trouble in the past because we did everything
// on the EDT) or if something broken in more recent QTJ. Or (maybe most
// likely) we're simply hitting some other threading strangeness, and
// using invokeLater() isolates us from that. Which is a nice way of
// saying that it's a hack.
//SwingUtilities.invokeLater(new Runnable() {
// public void run() {
init(parent, requestWidth, requestHeight, name, frameRate);
//}
//});
}
public void init(PApplet parent, int requestWidth, int requestHeight,
String name, int frameRate) {
this.parent = parent;
this.name = name;
this.frameRate = frameRate;
try {
QTSession.open();
} catch (QTException e) {
e.printStackTrace(System.out);
return;
}
try {
qdrect = new QDRect(requestWidth, requestHeight);
// workaround for bug with the intel macs
QDGraphics qdgraphics = null; //new QDGraphics(qdrect);
if (quicktime.util.EndianOrder.isNativeLittleEndian()) {
qdgraphics = new QDGraphics(QDConstants.k32BGRAPixelFormat, qdrect);
} else {
qdgraphics = new QDGraphics(QDGraphics.kDefaultPixelFormat, qdrect);
}
capture = new SequenceGrabber();
capture.setGWorld(qdgraphics, null);
channel = new SGVideoChannel(capture);
channel.setBounds(qdrect);
channel.setUsage(2); // what is this usage number?
capture.startPreview(); // maybe this comes later?
PixMap pixmap = qdgraphics.getPixMap();
raw = pixmap.getPixelData();
/*
if (name == null) {
channel.settingsDialog();
} else if (name.length() > 0) {
channel.setDevice(name);
}
*/
if ((name != null) && (name.length() > 0)) {
channel.setDevice(name);
}
dataRowBytes = raw.getRowBytes();
dataWidth = dataRowBytes / 4;
dataHeight = raw.getSize() / dataRowBytes;
if (dataWidth != requestWidth) {
crop = true;
cropX = 0;
cropY = 0;
cropW = requestWidth;
cropH = requestHeight;
}
// initialize my PImage self
super.init(requestWidth, requestHeight, RGB);
parent.registerDispose(this);
try {
captureEventMethod =
parent.getClass().getMethod("captureEvent",
new Class[] { Capture.class });
} catch (Exception e) {
// no such method, or an error.. which is fine, just ignore
}
runner = new Thread(this);
runner.start();
} catch (QTException qte) {
//} catch (StdQTException qte) {
//qte.printStackTrace();
int errorCode = qte.errorCode();
if (errorCode == Errors.couldntGetRequiredComponent) {
// this can happen when the capture device isn't available
// or wasn't shut down properly
parent.die("No capture could be found, " +
"or the VDIG is not installed correctly.", qte);
} else {
parent.die("Error while setting up Capture", qte);
}
} catch (Exception e) {
parent.die("Error while setting up Capture", e);
}
}
/**
* True if a frame is ready to be read.
* <PRE>
* // put this somewhere inside draw
* if (capture.available()) capture.read();
* </PRE>
* Alternatively, you can use captureEvent(Capture c) to notify you
* whenever available() is set to true. In which case, things might
* look like this:
* <PRE>
* public void captureEvent(Capture c) {
* c.read();
* // do something exciting now that c has been updated
* }
* </PRE>
*/
public boolean available() {
return available;
}
/**
* Set the video to crop from its original.
* <P>
* It seems common that captures add lines to the top or bottom
* of an image, so this can be useful for removing them.
* Internally, the pixel buffer size returned from QuickTime is
* often a different size than requested, so crop will be set
* more often than not.
*/
public void crop(int x, int y, int w, int h) {
/*
if (imageMode == CORNERS) {
w -= x; // w was actually x2
h -= y; // h was actually y2
}
*/
crop = true;
cropX = Math.max(0, x);
cropY = Math.max(0, y);
cropW = Math.min(w, dataWidth);
cropH = Math.min(dataHeight, y + h) - cropY;
// if size has changed, re-init this image
if ((cropW != width) || (cropH != height)) {
init(w, h, RGB);
}
}
/**
* Remove the cropping (if any) of the image.
* <P>
* By default, cropping is often enabled to trim out black pixels.
* But if you'd rather deal with them yourself (so as to avoid
* an extra lag while the data is moved around) you can shut it off.
*/
public void noCrop() {
crop = false;
}
public void read() {
//try {
//synchronized (capture) {
loadPixels();
synchronized (pixels) {
//System.out.println("read1");
if (crop) {
//System.out.println("read2a");
// f#$)(#$ing quicktime / jni is so g-d slow, calling copyToArray
// for the invidual rows is literally 100x slower. instead, first
// copy the entire buffer to a separate array (i didn't need that
// memory anyway), and do an arraycopy for each row.
if (data == null) {
data = new int[dataWidth * dataHeight];
}
raw.copyToArray(0, data, 0, dataWidth * dataHeight);
int sourceOffset = cropX + cropY*dataWidth;
int destOffset = 0;
for (int y = 0; y < cropH; y++) {
System.arraycopy(data, sourceOffset, pixels, destOffset, cropW);
sourceOffset += dataWidth;
destOffset += width;
}
} else { // no crop, just copy directly
//System.out.println("read2b");
raw.copyToArray(0, pixels, 0, width * height);
}
//System.out.println("read3");
available = false;
// mark this image as modified so that PGraphicsJava2D and
// PGraphicsOpenGL will properly re-blit and draw this guy
updatePixels();
//System.out.println("read4");
}
}
public void run() {
while ((Thread.currentThread() == runner) && (capture != null)) {
try {
synchronized (capture) {
capture.idle();
//read();
available = true;
if (captureEventMethod != null) {
try {
captureEventMethod.invoke(parent, new Object[] { this });
} catch (Exception e) {
System.err.println("Disabling captureEvent() for " + name +
" because of an error.");
e.printStackTrace();
captureEventMethod = null;
}
}
}
} catch (QTException e) {
errorMessage("run", e);
}
try {
Thread.sleep(1000 / frameRate);
} catch (InterruptedException e) { }
}
}
/**
* Set the frameRate for how quickly new frames are read
* from the capture device.
*/
public void frameRate(int iframeRate) {
if (iframeRate <= 0) {
System.err.println("Capture: ignoring bad frameRate of " +
iframeRate + " fps.");
return;
}
frameRate = iframeRate;
}
/**
* Called by applets to stop capturing video.
*/
public void stop() {
if (capture != null) {
try {
capture.stop(); // stop the "preview"
} catch (StdQTException e) {
e.printStackTrace();
}
capture = null;
}
runner = null; // unwind the thread
}
/**
* Called by PApplet to shut down video so that QuickTime
* can be used later by another applet.
*/
public void dispose() {
stop();
//System.out.println("calling dispose");
// this is important so that the next app can do video
QTSession.close();
}
/**
* General error reporting, all corraled here just in case
* I think of something slightly more intelligent to do.
*/
protected void errorMessage(String where, Exception e) {
parent.die("Error inside Capture." + where + "()", e);
}
/**
* Set the format to ask for from the video digitizer:
* TUNER, COMPOSITE, SVIDEO, or COMPONENT.
* <P>
* The constants are just aliases to the constants returned from
* QuickTime's getInputFormat() function, so any valid constant from
* that will work just fine.
*/
public void source(int which) {
try {
VideoDigitizer digitizer = channel.getDigitizerComponent();
int count = digitizer.getNumberOfInputs();
for (int i = 0; i < count; i++) {
//System.out.println("format " + digitizer.getInputFormat(i));
if (digitizer.getInputFormat(i) == which) {
digitizer.setInput(i);
return;
}
}
throw new RuntimeException("The specified source() is not available.");
} catch (StdQTException e) {
e.printStackTrace();
throw new RuntimeException("Could not set the video input source.");
}
}
/**
* Set the video format standard to use on the
* video digitizer: NTSC, PAL, or SECAM.
* <P>
* The constants are just aliases to the constants used for
* QuickTime's setInputStandard() function, so any valid
* constant from that will work just fine.
*/
public void format(int which) {
try {
VideoDigitizer digitizer = channel.getDigitizerComponent();
digitizer.setInputStandard(which);
} catch (StdQTException e) {
e.printStackTrace();
//throw new RuntimeException("Could not set the video input format");
}
}
/**
* Show the settings dialog for this input device.
*/
public void settings() {
try {
// fix for crash here submitted by hansi (stop/startPreview lines)
capture.stop();
// Whenever settingsDialog() is called, the boundries change,
// causing the image to be cropped. Fix for Bug #366
// http://dev.processing.org/bugs/show_bug.cgi?id=366
channel.setBounds(qdrect);
// Open the settings dialog (throws an Exception if canceled)
channel.settingsDialog();
} catch (StdQTException qte) {
int errorCode = qte.errorCode();
if (errorCode == Errors.userCanceledErr) {
// User only canceled the settings dialog, continue as we were
} else {
qte.printStackTrace();
throw new RuntimeException("Error inside Capture.settings()");
}
}
try {
// Start the preview again (unreachable if newly thrown exception)
capture.startPreview();
} catch (StdQTException qte) {
qte.printStackTrace();
}
}
/**
* Get a list of all available captures as a String array.
* i.e. println(Capture.list()) will show you the goodies.
*/
static public String[] list() {
try {
QTSession.open();
SequenceGrabber grabber = new SequenceGrabber();
SGVideoChannel channel = new SGVideoChannel(grabber);
SGDeviceList deviceList = channel.getDeviceList(0); // flags is 0
String listing[] = new String[deviceList.getCount()];
for (int i = 0; i < deviceList.getCount(); i++) {
listing[i] = deviceList.getDeviceName(i).getName();
}
// properly shut down the channel so the app can use it again
grabber.disposeChannel(channel);
QTSession.close();
return listing;
} catch (QTException qte) {
int errorCode = qte.errorCode();
if (errorCode == Errors.couldntGetRequiredComponent) {
throw new RuntimeException("Couldn't find any capture devices, " +
"read the video reference for more info.");
} else {
qte.printStackTrace();
throw new RuntimeException("Problem listing capture devices, " +
"read the video reference for more info.");
}
}
//return null;
}
}

View File

@@ -1,735 +0,0 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2004-07 Ben Fry and Casey Reas
The previous version of this code was developed by Hernando Barragan
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, or (at your option) any later version.
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 processing.core.*;
import java.io.*;
import java.lang.reflect.*;
import quicktime.*;
import quicktime.io.QTFile;
import quicktime.qd.*;
import quicktime.std.*;
import quicktime.std.movies.media.DataRef;
import quicktime.util.QTHandle;
import quicktime.util.RawEncodedImage;
@SuppressWarnings("deprecation")
public class Movie extends PImage implements PConstants, Runnable {
Method movieEventMethod;
String filename;
Thread runner;
PImage borderImage;
boolean removeBorders = true;
boolean play;
boolean repeat;
boolean available;
int fps;
/**
* The QuickTime for Java "Movie" object, made public
* in case anyone wants to play with it.
*/
public quicktime.std.movies.Movie movie;
QDRect movieRect;
QDGraphics movieGraphics;
boolean firstFrame = true;
RawEncodedImage raw;
/*
static {
try {
//System.out.println("jlp = " + System.getProperty("java.library.path"));
QTSession.open();
} catch (QTException e) {
e.printStackTrace();
}
// shutting off for 0116, hoping for better exception handling
QTRuntimeException.registerHandler(new QTRuntimeHandler() {
public void exceptionOccurred(QTRuntimeException e,
Object obj, String s, boolean flag) {
System.err.println("Problem inside Movie");
e.printStackTrace();
}
});
}
*/
public Movie(PApplet parent, String filename) {
this(parent, filename, 30);
}
public Movie(final PApplet parent, final String filename, final int ifps) {
// this creates a fake image so that the first time this
// attempts to draw, something happens that's not an exception
super(1, 1, RGB);
// http://dev.processing.org/bugs/show_bug.cgi?id=882
//SwingUtilities.invokeLater(new Runnable() {
//public void run() {
init(parent, filename, ifps);
//}
//});
}
public void init(PApplet parent, String filename, int fps) {
this.parent = parent;
this.fps = fps;
try {
QTSession.open();
} catch (QTException e) {
e.printStackTrace();
return;
}
// first check to see if this can be read locally from a file.
// otherwise, will have to load the file into memory, which is
// gonna make people unhappy who are trying to play back 50 MB
// quicktime movies with a locally installed piece exported
// as an application.
try {
try {
// first try a local file using the dataPath. usually this will
// work ok, but sometimes the dataPath is inside a jar file,
// which is less fun, so this will crap out.
File file = new File(parent.dataPath(filename));
if (file.exists()) {
movie = fromDataRef(new DataRef(new QTFile(file)));
//init(parent, movie, ifps);
//return;
}
} catch (Exception e) { } // ignored
// read from a folder local to the current working dir
// called "data". presumably this might be the data folder,
// though that should be caught above, if such a folder exists.
/*
if (movie == null) {
try {
File file = new File("data", filename);
if (file.exists()) {
movie = fromDataRef(new DataRef(new QTFile(file)));
init(parent, movie, ifps);
return;
}
} catch (QTException e2) { }
}
*/
// read from a file just hanging out in the local folder.
// this might happen when the video library is used with some
// other application, or the person enters a full path name
if (movie == null) {
try {
File file = new File(filename);
if (file.exists()) {
movie = fromDataRef(new DataRef(new QTFile(file)));
//init(parent, movie, ifps);
//return;
}
} catch (QTException e1) { }
}
} catch (SecurityException se) {
// online, whups. catch the security exception out here rather than
// doing it three times (or whatever) for each of the cases above.
}
// if the movie can't be read from a local file, it has to be read
// into a byte array and passed to qtjava. it's annoying that apple
// doesn't have something in the api to read a movie from a friggin
// InputStream, but oh well. it's their api.
if (movie == null) {
byte data[] = parent.loadBytes(filename);
//int dot = filename.lastIndexOf(".");
// grab the extension from the file, use mov if there is none
//String extension = (dot == -1) ? "mov" :
// filename.substring(dot + 1).toLowerCase();
try {
movie = fromDataRef(new DataRef(new QTHandle(data)));
} catch (QTException e) {
e.printStackTrace();
}
}
/*
URL url = null;
this.filename = filename; // for error messages
if (filename.startsWith("http://")) {
try {
url = new URL(filename);
DataRef urlRef = new DataRef(url.toExternalForm());
movie = fromDataRef(urlRef);
init(parent, movie, ifps);
return;
} catch (QTException qte) {
qte.printStackTrace();
return;
} catch (MalformedURLException e) {
e.printStackTrace();
return;
}
}
// updated for new loading style of 0096
ClassLoader cl = parent.getClass().getClassLoader();
url = cl.getResource("data/" + filename);
if (url != null) {
init(parent, url, ifps);
return;
}
try {
try {
File file = new File(parent.dataPath(filename));
if (file.exists()) {
movie = fromDataRef(new DataRef(new QTFile(file)));
init(parent, movie, ifps);
return;
}
} catch (Exception e) { } // ignored
try {
File file = new File("data", filename);
if (file.exists()) {
movie = fromDataRef(new DataRef(new QTFile(file)));
init(parent, movie, ifps);
return;
}
} catch (QTException e2) { }
try {
File file = new File(filename);
if (file.exists()) {
movie = fromDataRef(new DataRef(new QTFile(file)));
init(parent, movie, ifps);
return;
}
} catch (QTException e1) { }
} catch (SecurityException se) { } // online, whups
*/
if (movie == null) {
parent.die("Could not find movie file " + filename, null);
}
// we've got a valid movie! let's rock.
try {
// this is probably causing the 2 seconds of audio
// disabled pre-preroll on 0126 because of security problems
//movie.prePreroll(0, 1.0f);
movie.preroll(0, 1.0f);
// this has a possibility of running forever..
// should probably happen on the thread as well.
while (movie.maxLoadedTimeInMovie() == 0) {
movie.task(100);
// 0106: tried adding sleep time so this doesn't spin out of control
// works fine but doesn't really help anything
//try {
//Thread.sleep(5);
//} catch (InterruptedException e) { }
}
movie.setRate(1);
//fps = ifps;
// register methods
parent.registerDispose(this);
try {
movieEventMethod =
parent.getClass().getMethod("movieEvent",
new Class[] { Movie.class });
} catch (Exception e) {
// no such method, or an error.. which is fine, just ignore
}
// and now, make the magic happen
runner = new Thread(this);
runner.start();
} catch (QTException qte) {
qte.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
public Movie(PApplet parent, URL url) {
init(parent, url, 30);
}
public Movie(PApplet parent, URL url, int ifps) {
init(parent, url, ifps);
}
public void init(PApplet parent, URL url, int ifps) {
String externalized = url.toExternalForm();
System.out.println("externalized is " + externalized);
// qtjava likes file: urls to read file:/// not file:/
// so this changes them when appropriate
if (externalized.startsWith("file:/") &&
!externalized.startsWith("file:///")) {
externalized = "file:///" + url.getPath();
}
// the url version is the only available that can take
// an InputStream (indirectly) since it uses url syntax
//DataRef urlRef = new DataRef(requestFile);
try {
System.out.println(url);
System.out.println(externalized);
DataRef urlRef = new DataRef(externalized);
System.out.println(urlRef);
movie = fromDataRef(urlRef);
init(parent, movie, ifps);
} catch (QTException e) {
e.printStackTrace();
}
}
*/
/**
* Why does this function have to be so bizarre? i love the huge
* constants! i think they're neato. i feel like i'm coding for
* think pascal on my mac plus! those were happier times.
*/
private quicktime.std.movies.Movie fromDataRef(DataRef ref)
throws QTException {
return
quicktime.std.movies.Movie.fromDataRef(ref,
StdQTConstants4.newMovieAsyncOK |
StdQTConstants.newMovieActive);
}
/*
public void init(PApplet parent,
quicktime.std.movies.Movie movie, int ifps) {
this.parent = parent;
try {
// this is probably causing the 2 seconds of audio
movie.prePreroll(0, 1.0f);
movie.preroll(0, 1.0f);
// this has a possibility of running forever..
// should probably happen on the thread as well.
while (movie.maxLoadedTimeInMovie() == 0) {
movie.task(100);
// 0106: tried adding sleep time so this doesn't spin out of control
// works fine but doesn't really help anything
//try {
//Thread.sleep(5);
//} catch (InterruptedException e) { }
}
movie.setRate(1);
fps = ifps;
runner = new Thread(this);
runner.start();
// register methods
parent.registerDispose(this);
try {
movieEventMethod =
parent.getClass().getMethod("movieEvent",
new Class[] { Movie.class });
} catch (Exception e) {
// no such method, or an error.. which is fine, just ignore
}
} catch (QTException qte) {
qte.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
*/
public boolean available() {
return available;
}
public void read() {
try {
if (firstFrame) {
movieRect = movie.getBox();
//movieGraphics = new QDGraphics(movieRect);
if (quicktime.util.EndianOrder.isNativeLittleEndian()) {
movieGraphics =
new QDGraphics(QDConstants.k32BGRAPixelFormat, movieRect);
} else {
movieGraphics =
new QDGraphics(QDGraphics.kDefaultPixelFormat, movieRect);
}
}
Pict pict = movie.getPict(movie.getTime()); // returns an int
pict.draw(movieGraphics, movieRect);
PixMap pixmap = movieGraphics.getPixMap();
raw = pixmap.getPixelData();
// It needs to get at least a small part
// of the video to get the parameters
if (firstFrame) {
//int intsPerRow = pixmap.getRowBytes() / 4;
int movieWidth = movieRect.getWidth();
int movieHeight = movieRect.getHeight();
int j = raw.getRowBytes() - movieWidth*4;
// this doesn't round up.. does it need to?
int k = j / 4;
int dataWidth = movieWidth + k;
if (dataWidth != movieWidth) {
if (removeBorders) {
borderImage = new PImage(dataWidth, movieHeight, RGB);
} else {
movieWidth = dataWidth;
}
}
//int vpixels[] = new int[movieWidth * movieHeight];
//image = new PImage(vpixels, movieWidth, movieHeight, RGB);
super.init(movieWidth, movieHeight, RGB);
//parent.video = image;
firstFrame = false;
}
// this happens later (found by hernando)
//raw.copyToArray(0, image.pixels, 0, image.width * image.height);
loadPixels();
// this is identical to a chunk of code inside PCamera
// this might be a candidate to move up to PVideo or something
if (borderImage != null) { // need to remove borders
raw.copyToArray(0, borderImage.pixels,
0, borderImage.width * borderImage.height);
int borderIndex = 0;
int targetIndex = 0;
for (int i = 0; i < height; i++) {
System.arraycopy(borderImage.pixels, borderIndex,
pixels, targetIndex, width);
borderIndex += borderImage.width;
targetIndex += width;
}
} else { // just copy directly
raw.copyToArray(0, pixels, 0, width * height);
}
// ready to rock
//System.out.println("updating pixels");
//updatePixels(); // mark as modified
updatePixels();
} catch (QTException qte) {
qte.printStackTrace();
//QTSession.close(); // let dispose() handle it
}
}
/**
* Begin playing the movie, with no repeat.
*/
public void play() {
// if (runner != null) {
// stop();
// }
play = true;
// runner = new Thread(this);
// runner.start();
}
/**
* Begin playing the movie, with repeat.
*/
public void loop() {
play();
repeat = true;
}
/**
* Shut off the repeating loop.
*/
public void noLoop() {
repeat = false;
}
/**
* Pause the movie at its current time.
*/
public void pause() {
play = false;
//System.out.println("pause");
}
/**
* Stop the movie, and rewind.
*/
public void stop() {
play = false;
// runner = null;
try {
movie.setTimeValue(0);
} catch (StdQTException e) {
errorMessage("stop", e);
}
}
/**
* Set how often new frames are to be read from the movie.
* Does not actually set the speed of the movie playback,
* that's handled by the speed() method.
*/
public void frameRate(int ifps) {
if (ifps <= 0) {
System.err.println("Movie: ignoring bad frame rate of " +
ifps + " fps.");
} else {
fps = ifps;
}
}
/**
* Set a multiplier for how fast/slow the movie should be run.
* The default is 1.0.
* <UL>
* <LI>speed(2) will play the movie at double speed (2x).
* <LI>speed(0.5) will play at half speed.
* <LI>speed(-1) will play backwards at regular speed.
* </UL>
*/
public void speed(float rate) {
//rate = irate;
try {
movie.setRate(rate);
} catch (StdQTException e) {
errorMessage("speed", e);
}
}
/**
* Return the current time in seconds.
* The number is a float so fractions of seconds can be used.
*/
public float time() {
try {
return (float)movie.getTime() / (float)movie.getTimeScale();
} catch (StdQTException e) {
errorMessage("time", e);
}
return -1;
}
/**
* Jump to a specific location (in seconds).
* The number is a float so fractions of seconds can be used.
*/
public void jump(float where) {
try {
//movie.setTime(new TimeRecord(rate, where)); // scale, value
//movie.setTime(new TimeRecord(1, where)); // scale, value
int scaledTime = (int) (where * movie.getTimeScale());
movie.setTimeValue(scaledTime);
} catch (StdQTException e) {
errorMessage("jump", e);
}
}
/**
* Get the full length of this movie (in seconds).
*/
public float duration() {
try {
return (float)movie.getDuration() / (float)movie.getTimeScale();
} catch (StdQTException e) {
errorMessage("length", e);
}
return -1;
}
/*
public void play() {
if(!play) {
play = true;
}
start();
while( image == null) {
try {
Thread.sleep(5);
} catch (InterruptedException e) { }
}
pixels = image.pixels;
width = image.width;
height = image.height;
}
public void repeat() {
loop = true;
if(!play) {
play = true;
}
start();
while( image == null) {
try {
Thread.sleep(5);
} catch (InterruptedException e) { }
}
pixels = image.pixels;
width = image.width;
height = image.height;
}
public void pause() {
play = false;
}
*/
public void run() {
//System.out.println("entering thread");
while (Thread.currentThread() == runner) {
//System.out.print("<");
try {
//Thread.sleep(5);
Thread.sleep(1000 / fps);
} catch (InterruptedException e) { }
//System.out.print(">");
// this could be a lie, but..
if (play) {
//read();
//System.out.println("play");
available = true;
if (movieEventMethod == null) {
// If no special handling, then automatically read from the movie.
read();
} else {
try {
movieEventMethod.invoke(parent, new Object[] { this });
} catch (Exception e) {
System.err.println("error, disabling movieEvent() for " +
filename);
e.printStackTrace();
movieEventMethod = null;
}
}
try {
if (movie.isDone() && repeat) {
movie.goToBeginning();
}
} catch (StdQTException e) {
play = false;
errorMessage("rewinding", e);
}
//} else {
//System.out.println("no play");
}
//try {
//read();
//if (movie.isDone() && loop) movie.goToBeginning();
//} catch (QTException e) {
//System.err.println("Movie exception");
//e.printStackTrace();
//QTSession.close(); ??
//}
}
}
/**
* Call this to halt the movie from running, and stop its thread.
*/
public void dispose() {
stop();
runner = null;
QTSession.close();
}
/**
* General error reporting, all corraled here just in case
* I think of something slightly more intelligent to do.
*/
protected void errorMessage(String where, Exception e) {
parent.die("Error inside Movie." + where + "()", e);
}
}

View File

@@ -1,335 +0,0 @@
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2006 Daniel Shiffman
With minor modifications by Ben Fry for Processing 0125+
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 java.io.File;
import quicktime.*;
import quicktime.io.*;
import quicktime.qd.*;
import quicktime.std.*;
import quicktime.std.image.*;
import quicktime.std.movies.Movie;
import quicktime.std.movies.Track;
import quicktime.std.movies.media.VideoMedia;
import quicktime.util.*;
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.
* <BR> <BR>
* Please note that some constructors and variable names were altered
* slightly when the library was added to the Processing distribution.
* <PRE>
* // Declare MovieMaker object
* MovieMaker mm;
*
* void setup() {
* size(320, 240);
*
* // Create MovieMaker object with size, filename,
* // compression codec and quality, framerate
* mm = new MovieMaker(this, width, height, "drawing.mov", 30,
* MovieMaker.H263, MovieMaker.HIGH);
* background(160, 32, 32);
* }
*
* void draw() {
* stroke(7, 146, 168);
* strokeWeight(4);
*
* // Draw if mouse is pressed
* if (mousePressed) {
* line(pmouseX, pmouseY, mouseX, mouseY);
* }
*
* // Add window's pixels to movie
* mm.addFrame();
* }
*
* void keyPressed() {
* // Finish the movie if space bar is pressed!
* if (key == ' ') {
* mm.finish();
* }
* }
* </PRE>
*/
@SuppressWarnings("deprecation")
public class MovieMaker {
public static final int RAW = StdQTConstants.kRawCodecType;
public static final int ANIMATION = StdQTConstants.kAnimationCodecType;
public static final int BASE = StdQTConstants.kBaseCodecType;
public static final int BMP = StdQTConstants.kBMPCodecType;
public static final int CINEPAK = StdQTConstants.kCinepakCodecType;
public static final int COMPONENT = StdQTConstants.kComponentVideoCodecType;
public static final int CMYK = StdQTConstants.kCMYKCodecType;
public static final int GIF = StdQTConstants.kGIFCodecType;
public static final int GRAPHICS = StdQTConstants.kGraphicsCodecType;
public static final int H261 = StdQTConstants.kH261CodecType;
public static final int H263 = StdQTConstants.kH263CodecType;
// H.264 encoding, added because no constant is available in QTJava
public static final int H264 = QTUtils.toOSType("avc1");
public static final int JPEG = StdQTConstants.kJPEGCodecType;
public static final int MS_VIDEO = StdQTConstants.kMicrosoftVideo1CodecType;
public static final int MOTION_JPEG_A = StdQTConstants.kMotionJPEGACodecType;
public static final int MOTION_JPEG_B = StdQTConstants.kMotionJPEGBCodecType;
public static final int SORENSON = StdQTConstants.kSorensonCodecType;
public static final int VIDEO = StdQTConstants.kVideoCodecType;
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;
// Changed from 1000 to 600 in release 0154 to enable exact 30 fps output.
// http://dev.processing.org/bugs/show_bug.cgi?id=988
private int TIME_SCALE = 600;
// 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;
/**
* Create a movie with the specified width, height, and filename.
* The movie will be created at 15 frames per second.
* The codec will be set to RAW and quality set to HIGH.
*/
public MovieMaker(PApplet p, int _w, int _h, String _filename) {
this(p, _w, _h, _filename, 30, RAW, HIGH, 15);
}
/**
* Create a movie with the specified width, height, filename, and frame rate.
* The codec will be set to RAW and quality set to HIGH.
*/
public MovieMaker(PApplet p, int _w, int _h, String _filename, int _rate) {
this(p, _w, _h, _filename, _rate, RAW, HIGH, 15);
}
/**
* Create a movie with the specified width, height, filename, frame rate,
* and codec type and quality. Key frames will be set at 15 frames.
*/
public MovieMaker(PApplet p, int _w, int _h, String _filename, int _rate,
int _codecType, int _codecQuality) {
this(p, _w, _h, _filename, _rate, _codecType, _codecQuality, 15);
}
/**
* Create a movie with the specified width, height, filename, frame rate,
* codec type and quality, and key frame rate.
*/
public MovieMaker(PApplet p, int _w, int _h, String _filename, int _rate,
int _codecType, int _codecQuality,
int _keyFrameRate) {
parent = p;
width = _w;
height = _h;
rate = _rate;
try {
QTSession.open();
} catch (QTException e1) {
e1.printStackTrace();
}
try {
ImageDescription imgD = null;
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) {
try {
String path = parent.savePath(filename);
movFile = new QTFile(new 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 (e.errorCode() == Errors.noCodecErr) {
if (imageHandle == null) {
// This means QTImage.getMaxCompressionSize() failed
System.err.println("The specified codec is not supported, " +
"please ensure that the parameters are valid, " +
"and in the correct order.");
} else {
// If it's a -8961 error, quietly do it the other way
// (this happens when RAW is specified)
temporalSupported = false;
readyForFrames = true;
}
} else if (e.errorCode() == Errors.fBsyErr) {
System.err.println("The movie file already exists. " +
"Please delete it first.");
} else {
e.printStackTrace();
}
}
}
// A simple add function to just add whatever is in the parent window
public void addFrame() {
// http://dev.processing.org/bugs/show_bug.cgi?id=692
parent.flush();
parent.loadPixels();
addFrame(parent.pixels, parent.width, parent.height);
}
public void addFrame(int[] _pixels, int w, int h) {
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];
} 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();
}
}
/**
* Close out and finish the movie file.
*/
public void finish() {
try {
if (readyForFrames) {
//System.out.println("Finishing movie file.");
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 se) {
se.printStackTrace();
} catch (QTException qe) {
qe.printStackTrace();
}
}
public void dispose() {
if (readyForFrames) finish();
try {
QTSession.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}