// movie-capture.m
//
// A proof-of-concept command-line utility for capturing still images
// and movies.  Requires OS X Leopard.
//
// Written by Ron Garret, July 2008
//
// Compile with:
// gcc -o movie-capture -framework Foundation -framework QTKit
//     -framework Cocoa movie-capture.m
//

#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import <QTKit/QTKit.h>

@interface myThread:NSThread {
  int cnt;  // Number of frames captured, also used as a spinlock
  int flag; // Signal to capture a frame as a still image
}
@end

@implementation myThread:NSThread

// QTCaptureView delegate.  Captures the first frame as a still image
// and keeps a running count of captured frames used by the event driver
// thread to know when to quit.
- (CIImage *)view:(QTCaptureView *)view willDisplayImage :(CIImage *)image {
  if (flag) {
    flag = 0;
    NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCIImage:image];
    [[rep TIFFRepresentation]
      writeToFile:[NSString stringWithFormat:@"frame%d.tiff", cnt]
      atomically:NO];
    [[rep representationUsingType:NSJPEGFileType
	  properties:[NSDictionary
		       dictionaryWithObject:[NSNumber numberWithFloat:0.9]
		       forKey:NSImageCompressionFactor]]
      writeToFile:[NSString stringWithFormat:@"frame%d.jpg", cnt]
      atomically:NO];
  }
  cnt++;
  return image;
}

// QTCaptureMovieFileOutput delegate method, called when all video
// frames have been written to the file
- (void)captureOutput:(QTCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL forConnections:(NSArray *)connections dueToError:(NSError *)error
{
  if (error) {
    NSLog(@"Error %@", error);
    return;
  }
  NSLog(@"Recorded:\n%llu Bytes\n%@ Duration",
	[captureOutput recordedFileSize],
	QTStringFromTime([captureOutput recordedDuration]));
  cnt=-1;  // Signal that it's safe to quit
}

- (void)windowWillClose:(NSNotification *)notification { cnt = 1000000; }
- (void)snap { flag = 1; }

// Main event driver
- (void)main {

  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSError *err;

  cnt = flag = 0;

  // Snarf the first available video device
  NSArray *a = [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
  QTCaptureDevice *cam = [a objectAtIndex:0];
  if ([cam open:&err] != YES) {
    NSLog(@"Error opening camera: %@", err);
  }
  QTCaptureDeviceInput *in = [[QTCaptureDeviceInput alloc] initWithDevice:cam];
  NSLog(@"Capturing from: %@", cam);

  // Set up the output file
  QTCaptureMovieFileOutput *out = [[QTCaptureMovieFileOutput alloc] init];
  [out setDelegate:self];
  [out recordToOutputFileURL:[NSURL fileURLWithPath:@"foo.mov"]];
  NSLog(@"Recording to: %@", [out outputFileURL]);

  // Set up the capture session
  QTCaptureSession *session = [[QTCaptureSession alloc] init];
  if ([session addInput:in error:&err] != YES) {
    NSLog(@"Error adding input to capture session: %@", err);
  }
  if ([session addOutput:out error:&err] != YES) {
    NSLog(@"Error adding output to capture session: %@", err);
  }

  // Set up a monitor window.  This is actually necessary because we're
  // setting ourselves up as the delegate for the QTCaptureView in order
  // to count frames so we know when to quit.
  QTCaptureView *v = [[QTCaptureView alloc] init];
  [v setCaptureSession:session];
  [v setDelegate:self];

  // Need a window, otherwise the view does nothing.
  NSWindow *win = [[NSWindow alloc] initWithContentRect:NSMakeRect
				    (100, 100, 640, 530)
                                    styleMask:15
                                    backing:NSBackingStoreBuffered
                                    defer:NO];

  [win setDelegate:self];
  [v setFrame:NSMakeRect(0,50,640,480)];
  [[win contentView] addSubview:v];

  // Make a SNAP button.
  NSRect frame = NSMakeRect(300, 10, 60, 30);
  NSButton *button = [[NSButton alloc] initWithFrame:frame];
  [button setTitle:@"Snap"];
  [button setButtonType:NSMomentaryPushInButton];
  [button setBezelStyle:NSRoundedBezelStyle];
  [button setTarget:self];
  [button setAction:@selector(snap)];
  [[win contentView] addSubview:button];

  // Actually displaying the window is optional
  [win makeKeyAndOrderFront:win];

  // Let 'er rip!
  [session startRunning];
  
  // Wait for a few frames to be accumulated.
  while (cnt<100000) {
    NSLog(@"Captured %d frames", cnt);
    sleep(1);
  }

  // Stop the recording
  [out recordToOutputFileURL:nil];

  // Wait for all the frames to be written
  while (cnt>0) usleep(10000);

  // Must do this or we get a bus error when we try to stop the session
  [session removeOutput:out];

  NSLog(@"Stopping session");
  [session stopRunning];
  NSLog(@"Session stopped");

  [NSApp terminate:self];
  [pool release];
}

@end


int main( int argc, const char* argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  [NSApplication sharedApplication];

  // Start the event driver thread
  [[[myThread alloc] init] start];

  [NSApp run];

  // This code never seems to run.  Do I really need it?  Or am I doing
  // something wrong when I terminate the app?
  NSLog(@"Exiting");
  [pool release];
  return 0;
}

