Files
FreeJ/xcode/QTExporter.mm
xant 3b4b091f6e merging more stuff from 6b738edfc17744259a1289af02d977bc65036d11
Conflicts:

        xcode/CVScreenView.mm
        xcode/English.lproj/MainMenu.nib/designable.nib
        xcode/English.lproj/MainMenu.nib/keyedobjects.nib
        xcode/Exporter.h
2010-03-27 23:54:02 +01:00

424 lines
13 KiB
Plaintext

/* FreeJ
* (c) Copyright 2009 Andrea Guzzo <xant@dyne.org>
*
* This source code is free software; you can redistribute it and/or
* modify it under the terms of the GNU Public License as published
* by the Free Software Foundation; either version 3 of the License,
* or (at your option) any later version.
*
* This source code 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.
* Please refer to the GNU Public License for more details.
*
* You should have received a copy of the GNU Public License along with
* this source code; if not, write to:
* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#import <CVScreenView.h>
#import <QTExporter.h>
#include <fps.h>
@implementation QTExporter
#ifdef __x86_64
- (id)initWithScreen:(CVScreenView *)cvscreen { return [super init];}
- (BOOL)setOutputFile:(NSString *)path {
fprintf(stderr, "Export not possible: Quicktime is only available on 32bit platforms.\n");
return NO;
}
- (void)addImage:(CIImage *)image {}
- (BOOL)startExport {
fprintf(stderr, "Export not possible: Quicktime is only available on 32bit platforms.\n");
return NO;
}
- (void)stopExport {}
- (BOOL)isRunning {return NO;}
#else
- (id)initWithScreen:(CVScreenView *)cvscreen
{
//mDataHandlerRef = nil;
mMovie = nil;
outputFile = nil;
screen = cvscreen;
savedMovieAttributes = [NSDictionary
dictionaryWithObjects:
[NSArray arrayWithObjects:
[NSNumber numberWithBool:YES],
[NSNumber numberWithBool:YES],
[NSNumber numberWithLong:'mpg4'], // TODO - allow to select the codec
nil]
forKeys:
[NSArray arrayWithObjects:
QTMovieFlatten, QTMovieExport, QTMovieExportType, nil]];
// when adding images we must provide a dictionary
// specifying the codec attributes
encodingProperties = [[NSDictionary dictionaryWithObjectsAndKeys:@"mp4v",
QTAddImageCodecType,
[NSNumber numberWithLong:codecNormalQuality],
QTAddImageCodecQuality,
nil] retain];
lock = [[NSRecursiveLock alloc] init];
memset(&lastTimestamp, 0, sizeof(lastTimestamp));
lastImage = nil;
return [super init];
}
- (void)dealloc
{
[savedMovieAttributes release];
[encodingProperties release];
[lock release];
[super dealloc];
}
/* XXX - deprecated on snow leopard (old versions of the QT frameworks (<= 7.2.1) are not supported anymore */
/*
- (Movie)quicktimeMovieFromTempFile:(DataHandler *)outDataHandler error:(OSErr *)outErr
{
*outErr = -1;
// generate a name for our movie file
NSString *tempName = [NSString stringWithCString:tmpnam(nil)
encoding:[NSString defaultCStringEncoding]];
if (!tempName)
return nil;
Handle dataRefH = nil;
OSType dataRefType;
// create a file data reference for our movie
*outErr = QTNewDataReferenceFromFullPathCFString((CFStringRef)tempName,
kQTPOSIXPathStyle,
0,
&dataRefH,
&dataRefType);
if (*outErr != noErr)
return nil;
// create a QuickTime movie from our file data reference
Movie qtMovie = nil;
CreateMovieStorage (dataRefH,
dataRefType,
'TVOD',
smSystemScript,
newMovieActive,
outDataHandler,
&qtMovie);
*outErr = GetMoviesError();
if (*outErr != noErr) goto cantcreatemovstorage;
return qtMovie;
// error handling
cantcreatemovstorage:
DisposeHandle(dataRefH);
return nil;
}
*/
- (BOOL)pathExists:(NSString *)aFilePath
{
NSFileManager *defaultMgr = [NSFileManager defaultManager];
return [defaultMgr fileExistsAtPath:aFilePath];
}
//
// writeSafelyToURL
//
// Write the document to a movie file
//
//
- (BOOL)writeSafelyToURL:(NSURL *)absoluteURL
{
BOOL success = NO;
if ([self pathExists:[absoluteURL path]] == YES)
{
// movie file already exists, so we'll just update
// the movie resource
success = [mMovie updateMovieFile];
}
else
{
success = [mMovie writeToFile:outputFile withAttributes:savedMovieAttributes];
// movie file does not exist, so we'll flatten our in-memory
// movie to the file
// now we can release our old in-memory movie
[mMovie release];
mMovie = nil;
// ...and re-acquire our movie from the new movie file
mMovie = [QTMovie movieWithFile:[absoluteURL path] error:nil];
[mMovie retain];
// set the new movie to the view
//[[mWinController movieView] setMovie:mMovie];
}
return success;
}
//
// addImage
//
// given an array a CIImage pointer, convert it to NSImage *
// and add the resulting image to the movie as a new MPEG4 frame
//
- (void)addImage:(CIImage *)image
{
/*
NSAffineTransform *scaleTransform = [NSAffineTransform transform];
float scaleFactor = 144/[image extent].size.height;
[scaleTransform scaleBy:scaleFactor];
CIFilter *scaleFilter = [CIFilter filterWithName:@"CIAffineTransform"];
[scaleFilter setDefaults]; // set the filter to its default values
[scaleFilter setValue:scaleTransform forKey:@"inputTransform"];
[scaleFilter setValue:image forKey:@"inputImage"];
CIImage *scaledImage = [scaleFilter valueForKey:@"outputImage"];
NSImage *nsImage = [[NSImage alloc] initWithSize:NSMakeSize([scaledImage extent].size.width, [scaledImage extent].size.height)];
[nsImage addRepresentation:[NSCIImageRep imageRepWithCIImage:scaledImage]];
*/
NSImage *nsImage = [[NSImage alloc] initWithSize:NSMakeSize([image extent].size.width, [image extent].size.height)];
[nsImage addRepresentation:[NSCIImageRep imageRepWithCIImage:image]];
// create a QTTime value to be used as a duration when adding
// the image to the movie
QTTime duration;
CVTimeStamp now;
memset(&now, 0 , sizeof(now));
if (CVDisplayLinkGetCurrentTime((CVDisplayLinkRef)[screen getDisplayLink], &now) == kCVReturnSuccess) {
if (lastImage) {
int64_t timedelta = lastTimestamp.videoTime?now.videoTime-lastTimestamp.videoTime:now.videoTimeScale/25;
duration = QTMakeTime(timedelta, now.videoTimeScale);
memcpy(&lastTimestamp, &now, sizeof(lastTimestamp));
[QTMovie enterQTKitOnThread];
// Adds an image for the specified duration to the QTMovie
[mMovie addImage:lastImage
forDuration:duration
withAttributes:encodingProperties];
// free up our image object
if ([self isRunning])
[self writeSafelyToURL:[NSURL fileURLWithPath:outputFile]];
[QTMovie exitQTKitOnThread];
}
if (lastImage)
[lastImage release];
lastImage = nsImage;
} else {
/* TODO - Error Messages */
}
bail:
return;
}
- (BOOL)openOutputMovie
{
/*
NOTES ABOUT CREATING A NEW ("EMPTY") MOVIE AND ADDING IMAGE FRAMES TO IT
In order to compose a new movie from a series of image frames with QTKit
it is of course necessary to first create an "empty" movie to which these
frames can be added. Actually, the real requirements (in QuickTime terminology)
for such an "empty" movie are that it contain a writable data reference. A
movie with a writable data reference can then accept the addition of image
frames via the -addImage method.
Prior to QuickTime 7.2.1, QTKit did not provide a QTMovie method for creating a
QTMovie with a writable data reference. In this case, we can use the native
QuickTime API CreateMovieStorage() to create a QuickTime movie with a writable
data reference (in our example below we use a data reference to a file). We then
use the QTKit movieWithQuickTimeMovie: method to instantiate a QTMovie from this
native QuickTime movie.
Finally, images are added to the movie as movie frames using -addImage.
NEW IN QUICKTIME 7.2.1
QuickTime 7.2.1 now provides a new method:
- (id)initToWritableFile:(NSString *)filename error:(NSError **)errorPtr;
to create a QTMovie with a writable data reference. This eliminates the need to
use the native QuickTime API CreateMovieStorage() as described above.
The code below checks first to see if this new method initToWritableFile: is
available, and if so it will use it rather than use the native API.
*/
//[QTMovie enterQTKitOnThread];
// Check first if the new QuickTime 7.2.1 initToWritableFile: method is available
if ([[[[QTMovie alloc] init] autorelease] respondsToSelector:@selector(initToWritableFile:error:)] == YES)
{
NSError *error;
NSFileHandle *ofile;
ofile = [NSFileHandle fileHandleForUpdatingAtPath:outputFile];
if (ofile) {
unsigned long ticks;
[ofile truncateFileAtOffset:0];
[ofile synchronizeFile];
[ofile closeFile];
// let the os really sync the filesystem before trying to reopen the file
Delay(5, &ticks);
}
// Create a QTMovie with a writable data reference
mMovie = [[QTMovie alloc] initToWritableFile:outputFile error:&error];
if (!mMovie) {
NSLog(@"Can't open output file: %@ : %@", outputFile, [error localizedFailureReason]);
return NO;
}
}
/*
else
{
// The QuickTime 7.2.1 initToWritableFile: method is not available, so use the native
// QuickTime API CreateMovieStorage() to create a QuickTime movie with a writable
// data reference
OSErr err;
// create a native QuickTime movie
Movie qtMovie = [self quicktimeMovieFromTempFile:&mDataHandlerRef error:&err];
if (nil == qtMovie) {
[lock unlock];
return NO;
}
// instantiate a QTMovie from our native QuickTime movie
mMovie = [QTMovie movieWithQuickTimeMovie:qtMovie disposeWhenDone:YES error:nil];
if (!mMovie || err) {
[lock unlock];
return NO;
}
}
*/
// mark the movie as editable
[mMovie setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieEditableAttribute];
// keep it around until we are done with it...
[mMovie retain];
//[mMovie setIdling:NO];
//[mMovie setAttribute:[NSNumber numberWithLong:1000000000] forKey:QTMovieTimeScaleAttribute];
//[QTMovie exitQTKitOnThread];
return YES;
}
- (void) exporterThread:(id)arg
{
NSAutoreleasePool *pool;
FPS fps;
fps.init(25); // XXX
if (!encodingProperties)
;// TODO - Handle error condition
while ([self isRunning]) {
pool = [[NSAutoreleasePool alloc] init];
[self addImage:[screen exportSurface]];
/*
fps.calc();
fps.delay();
*/
[pool release];
}
}
//
// buildQTKitMovie
//
// Build a QTKit movie from a series of image frames
//
//
- (BOOL)startExport:(int)fps width:(int)w height:(int)h
{
#pragma unused (fps)
#pragma unused (w)
#pragma unused (h)
if (!outputFile)
outputFile = [[NSString stringWithCString:DEFAULT_OUTPUT_FILE encoding:NSUTF8StringEncoding] retain];
if ([self openOutputMovie]) {
[NSThread detachNewThreadSelector:@selector(exporterThread:)
toTarget:self withObject:nil];
return YES;
}
return NO;
}
- (BOOL)setOutputFile:(NSString *)path
{
if (outputFile)
[outputFile release];
outputFile = [path retain];
return YES;
}
- (void)setOutputQuality:(int)quality
{
switch (quality) {
case 0:
[encodingProperties setObject:[NSNumber numberWithLong:codecLowQuality] forKey:QTAddImageCodecQuality];
break;
case 1:
[encodingProperties setObject:[NSNumber numberWithLong:codecNormalQuality] forKey:QTAddImageCodecQuality];
break;
default:
[encodingProperties setObject:[NSNumber numberWithLong:codecHighQuality] forKey:QTAddImageCodecQuality];
}
}
- (void)setOutputFormat:(NSNumber *)format Codec:(NSString *)codec
{
if (savedMovieAttributes)
[savedMovieAttributes release];
savedMovieAttributes = [[NSDictionary
dictionaryWithObjects:
[NSArray arrayWithObjects:
[NSNumber numberWithBool:YES],
[NSNumber numberWithBool:YES],
format,
nil]
forKeys:
[NSArray arrayWithObjects:
QTMovieFlatten, QTMovieExport, QTMovieExportType, nil]] retain];
[encodingProperties setObject:codec forKey:QTAddImageCodecType];
}
- (void)stopExport
{
[lock lock];
if (mMovie)
[mMovie release];
mMovie = nil;
/*
if (mDataHandlerRef) {
CFRelease(mDataHandlerRef);
mDataHandlerRef = nil;
}
*/
[lock unlock];
}
- (BOOL)isRunning
{
return mMovie?YES:NO;
}
#endif // no __x86_64
@end