Difference between revisions of "Movie cocoaqt"

From The Foundry MODO SDK wiki
Jump to: navigation, search
Line 7: Line 7:
 
===Class Declaration===
 
===Class Declaration===
  
We want to have our class create a movie object, so we have our class inherit from CLxImpl_Movie.
+
We want our class to create a movie object, so we inherit from [[Movie:_Server_basics#Sample_Methods|CLxImpl_Movie]]. Our class includes all the basic methods of a movie class. First, our mov_BeginMovie class initiates the saving and provides the file path and the size of the frame. Next, our mov_SetFrameRate function sets the frame rate. After that, our mov_AddImage appends an image to the movie every time it is called. After that, the mov_AddAudio function adds an audio track to the movie file. Finally, our EndMovie function closes the movies file and cleans up the temp files.
  
 
  class CCocoaQuickTimeMovie : public CLxImpl_Movie
 
  class CCocoaQuickTimeMovie : public CLxImpl_Movie
Line 29: Line 29:
 
===[[Server_Tags|Server Tags]]===
 
===[[Server_Tags|Server Tags]]===
  
The tags here indicate that the main class will be a server of the movie type that will have the extension .mov named Express Quicktime
+
Servers tags are examined when the server is initialized, and give information about the server. This is accomplished here by taking a descinfo[] array and associating the relevant data with the corresponding flags. The tags here indicate that the main class will be a server of the movie type that will have the extension .mov named Express Quicktime.
  
 
  LXtTagInfoDesc CCocoaQuickTimeMovie::descInfo[] = {
 
  LXtTagInfoDesc CCocoaQuickTimeMovie::descInfo[] = {
Line 41: Line 41:
 
===[[Initialize_(index)|Initialize]]===
 
===[[Initialize_(index)|Initialize]]===
  
We export a server of the movie type dependent on the CCocoaQuickTimeMovie class with the "quicktime" name.
+
Intialize is called when we add the plugin to modo, and is the utility that exports the server. In this Initialize() we export a server of the movie type dependent on the CCocoaQuickTimeMovie class with the "quicktime" name. The LXx_ADD_SERVER method is simply a wrapper that is identical to normal method of adding a server, with the arguments being (interface_to_be_added, class_you_depend_on, server_name). In this case we add the Movie interface, depend on the CCocoaQuickTimeMove, and name our server "quicktime".
  
 
  void
 
  void
Line 51: Line 51:
 
===Helper Functions===
 
===Helper Functions===
  
This function takes a cg image and transforms it into a ns image, the apple format.
+
Apple uses the NS image format for its movie frames, so we need to have some function to convert our format(CGImage) to NS. This function does exactly that. We call this function in our mov_AddImage function in order to convert the CGImage we have constructed.  
  
 
  static NSImage *
 
  static NSImage *
Line 57: Line 57:
 
         CGImageRef image)
 
         CGImageRef image)
 
  {
 
  {
         NSRect imageRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
+
         ...
        CGContextRef imageContext = nil;
+
        NSImage *newImage = nil;
+
   
+
        // Get the image dimensions.
+
        imageRect.size.height = CGImageGetHeight(image);
+
        imageRect.size.width = CGImageGetWidth(image);
+
     
+
        // Create a new image to receive the Quartz image data.
+
        newImage = [[NSImage alloc] initWithSize:imageRect.size];
+
        [newImage lockFocus];
+
     
+
        // Get the Quartz context and draw.
+
        imageContext = (CGContextRef)[[NSGraphicsContext currentContext]
+
                                      graphicsPort];
+
        CGContextDrawImage(imageContext, *(CGRect*)&imageRect, image);
+
        [newImage unlockFocus];
+
     
+
        return newImage;
+
 
  }
 
  }
  
This function creates a temporary audio file with the audio object.
+
This function creates a temporary audio file with the audio object. Most importantly, if the function is unable to read or write the audio object, it returns an error code. This function is called in mov_AddAudio in order to verify that the audio object passed in is valid.
  
 
  static LxResult
 
  static LxResult
Line 85: Line 67:
 
         CLxUser_Audio *audio)
 
         CLxUser_Audio *audio)
 
  {
 
  {
        LXtAudioMetrics m;
+
       ...
        LxResult res;
+
        OSStatus err;
+
        NSURL *toURL;
+
        AudioBufferList audioBufferList;
+
        UInt32 bufferSize;
+
        char *buffer;
+
        ExtAudioFileRef outfile;
+
        int eos;
+
        unsigned int readFrames;
+
     
+
        toURL = [NSURL fileURLWithPath: filePath];
+
   
+
        audio->Metrics (&m);
+
   
+
        AudioStreamBasicDescription outFormat = {0};
+
        AudioFileTypeID audioFileType;
+
     
+
        outFormat.mBitsPerChannel  = m.type;
+
        outFormat.mSampleRate      = (Float64) m.frequency;
+
        outFormat.mFormatID        = kAudioFormatLinearPCM;
+
        outFormat.mChannelsPerFrame = m.channels;
+
        outFormat.mFramesPerPacket  = 1;
+
        outFormat.mBytesPerFrame    = outFormat.mBitsPerChannel / 8 * outFormat.mChannelsPerFrame;
+
        outFormat.mBytesPerPacket  = outFormat.mBytesPerFrame * outFormat.mFramesPerPacket;
+
        audioFileType = kAudioFileWAVEType;
+
        outFormat.mFormatFlags      = kAudioFormatFlagIsPacked;
+
        if (m.type == 16)
+
                outFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
+
        else if (m.type == 32)
+
                outFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
+
        
+
        // Create a temp audio file.
+
        err = ExtAudioFileCreateWithURL ((CFURLRef) toURL,
+
                                        audioFileType,
+
                                        &outFormat,
+
                                        NULL,
+
                                        kAudioFileFlags_EraseFile,
+
                                        &outfile);
+
        if (err != noErr) {
+
                NSLog (@"[ExtAudioFileCreateWithURL] err %ld\n", err);
+
                NSLog (@"[toURL] %@\n", toURL);
+
                NSLog (@"mSampleRate %f\n", outFormat.mSampleRate);
+
                NSLog (@"mFormatID %s\n", &outFormat.mFormatID);
+
                NSLog (@"mFormatFlags %x\n", outFormat.mFormatFlags);
+
                NSLog (@"mBytesPerPacket %u\n", outFormat.mBytesPerPacket);
+
                NSLog (@"mFramesPerPacket %u\n", outFormat.mFramesPerPacket);
+
                NSLog (@"mBytesPerFrame %u\n", outFormat.mBytesPerFrame);
+
                NSLog (@"mChannelsPerFrame %u\n", outFormat.mChannelsPerFrame);
+
                NSLog (@"mBitsPerChannel %u\n", outFormat.mBitsPerChannel);
+
                return LXe_FAILED;
+
        }
+
   
+
        readFrames = 1024;
+
        bufferSize = sizeof (char) * readFrames * outFormat.mBytesPerPacket;
+
        buffer = (char *) malloc (bufferSize);
+
   
+
        audioBufferList.mNumberBuffers = 1;
+
        audioBufferList.mBuffers[0].mNumberChannels = outFormat.mChannelsPerFrame;
+
        audioBufferList.mBuffers[0].mDataByteSize = bufferSize;
+
        audioBufferList.mBuffers[0].mData = buffer;
+
   
+
   
+
        // Read audio data from audio object and write it into the temp file.
+
        audio->Seek (0);
+
        while (1) {
+
                readFrames = 1024;
+
                res = audio->Read (&readFrames, buffer, &eos);
+
                if (LXx_FAIL (res))
+
                        return LXe_FAILED;
+
   
+
                if (readFrames == 0)
+
                        break;
+
   
+
                err = ExtAudioFileWrite (outfile, (UInt32) readFrames, &audioBufferList);
+
                if (err != noErr)
+
                        return LXe_FAILED;
+
        }
+
 
+
        free (buffer);
+
        ExtAudioFileDispose (outfile);
+
        return LXe_OK;
+
 
  }  
 
  }  
  
Line 180: Line 81:
 
         int                      flags)
 
         int                      flags)
 
  {
 
  {
        /*
+
    ...
        * Set up a dictionary with the codec attributes to be used when
+
        * creating our movie. For now, we always use "mp4v" for MPEG4.
+
        */
+
        myDict = [NSDictionary dictionaryWithObjectsAndKeys:@"mp4v",
+
                  QTAddImageCodecType,
+
                  [NSNumber numberWithLong:codecHighQuality],
+
                  QTAddImageCodecQuality,
+
                  nil];
+
+
        nsStringPath = [NSString stringWithUTF8String: fname];
+
        [nsStringPath retain];
+
         
+
        if (!nsStringPath)
+
                return LXe_FAILED;
+
+
        /*
+
        * If we made it this far, we already have permission to overwrite
+
        * an existing file.
+
        */
+
if ([[NSFileManager defaultManager] fileExistsAtPath: nsStringPath]) {
+
                if ([[NSFileManager defaultManager] removeFileAtPath:nsStringPath handler:nil]) {
+
                        NSLog(@"existing file - removed successfully");
+
                }
+
                else {
+
                        // We can't continue with the previous file.
+
                        return LXe_FAILED;
+
                }
+
        }
+
+
        // Create a QTMovie with a writable data reference.
+
        NSError *error = nil;
+
        mMovie = [[QTMovie alloc] initToWritableFile:nsStringPath error:&error];
+
+
        // Mark the movie as editable.
+
        [mMovie setAttribute:[NSNumber numberWithBool:YES] forKey:QTMovieEditableAttribute];
+
         
+
        // Keep it around until we are done with it.
+
        [mMovie retain];
+
+
        // Reset audio filename.
+
        audioFileName = nil;
+
+
        return LXe_OK;
+
 
  }
 
  }
  
Line 232: Line 90:
 
         int frate)
 
         int frate)
 
  {
 
  {
        frameRate = frate;
+
      ...
+
        return LXe_OK;
+
 
  }
 
  }
  
Line 243: Line 99:
 
         ILxUnknownID img)
 
         ILxUnknownID img)
 
  {
 
  {
        CLxUser_Image image (img);
+
      ...
        static int releaseTime = 0;
+
        NSAutoreleasePool *pool = NULL;
+
+
        // Set up our dimensions.
+
        unsigned int iw, ih;
+
        image.Size (&iw, &ih);
+
        unsigned int rowBytes = (iw * 3 + 3) & ~3;
+
+
        // Create the line and frame buffers.
+
        LXtImageByte *line, *lineBuffer, *frameBuffer, *frameLine;
+
        lineBuffer = (LXtImageByte *) malloc (rowBytes);
+
        frameBuffer = (LXtImageByte *)malloc (ih * rowBytes);
+
        if (frameBuffer) {
+
                /*
+
                * There is apparently a bug in NSImage and NOT QTKit -
+
                * http://www.mailinglistarchive.com/html/quartz-dev@lists.apple.com/2008-07/msg00552.html
+
                * which doesn't free the images passed into addImage - suspect that addImage add-ref's the NSImage
+
                * or some similar interplay.  Adding autorelease pool here is probably heavy-handed but
+
                * doesn't really affect performance.
+
                */
+
                 
+
                pool = [[NSAutoreleasePool alloc] init];
+
+
                // Copy the scanlines into a raw frame buffer.
+
                frameLine = frameBuffer;
+
                for (unsigned y = 0; y < ih; ++y) {
+
                        line = (LXtImageByte *) image.GetLine (
+
                                y, LXiIMP_RGB24, lineBuffer);
+
                        memcpy (frameLine, line, rowBytes);
+
                        frameLine += rowBytes;
+
                }
+
+
                // Construct a CGImage from the raw frame buffer.
+
                CGDataProviderRef dataProvider;
+
                CGImageRef cgImageRef;
+
                CGColorSpaceRef colorSpace;
+
+
                dataProvider = CGDataProviderCreateWithData (
+
                        NULL, frameBuffer, ih * rowBytes, NULL);
+
                colorSpace = CGColorSpaceCreateDeviceRGB ();
+
                cgImageRef = CGImageCreate (iw, ih, 8, 24, rowBytes, colorSpace,
+
                        kCGImageAlphaNone, dataProvider, NULL, 1, kCGRenderingIntentDefault);
+
               
+
                CGColorSpaceRelease (colorSpace);
+
                CGDataProviderRelease (dataProvider);
+
               
+
                // Convert the CGImage to an NSImage.
+
                NSImage *anImage = cgImageToNSImage (cgImageRef);
+
+
                // Free both the CGImage and the raw image buffer.
+
                CGImageRelease (cgImageRef);
+
                free (frameBuffer);
+
+
                // Calculate the duration of the image during the movie.
+
                long long timeValue = 1;
+
                long timeScale      = frameRate;
+
                QTTime duration    = QTMakeTime(timeValue, timeScale);
+
+
                // Add the NSImage for the specified duration to the QTMovie.
+
                [mMovie addImage:anImage
+
                        forDuration:duration
+
                        withAttributes:myDict];
+
               
+
                // Free our image object.
+
                [anImage release];
+
                [pool release];
+
        }
+
        free (lineBuffer);
+
+
        return LXe_OK;
+
 
  }
 
  }
  
Line 322: Line 108:
 
         ILxUnknownID au)
 
         ILxUnknownID au)
 
  {
 
  {
         CLxUser_Audio audio (au);
+
         ...
        QTMovie *audioTmpMovie = nil;
+
        NSFileManager *fileManager;
+
        NSError *error;
+
        char tempName[1024];
+
+
        strcpy (tempName, tmpnam (nil));
+
        strcat (tempName, ".wav"); // Append the suffix for some reason
+
        audioFileName = [NSString stringWithUTF8String: tempName];
+
        [audioFileName retain];
+
+
        if (LXx_FAIL (CreateAudioTempFile (audioFileName, &audio)))
+
                return LXe_FAILED;
+
+
        audioTmpMovie = [QTMovie movieWithFile:audioFileName error:&error];
+
        if (audioTmpMovie) {
+
                NSArray *audioTracks = [audioTmpMovie tracksOfMediaType:QTMediaTypeSound];
+
                QTTrack *audioTrack  = nil;
+
                if ([audioTracks count] > 0)
+
                        audioTrack = [audioTracks objectAtIndex:0];
+
       
+
                if (audioTrack) {
+
                        QTTimeRange totalRange;
+
                        totalRange.time = QTZeroTime;
+
                        totalRange.duration = [[audioTmpMovie attributeForKey:QTMovieDurationAttribute] QTTimeValue];
+
                        [mMovie insertSegmentOfTrack:audioTrack timeRange:totalRange atTime:QTZeroTime];
+
                }
+
        }
+
+
        return LXe_OK;
+
 
  }
 
  }
  
Line 359: Line 116:
 
  CCocoaQuickTimeMovie::mov_EndMovie (void)
 
  CCocoaQuickTimeMovie::mov_EndMovie (void)
 
  {
 
  {
         if (!audioFileName) {
+
         ...
                BOOL success = [mMovie updateMovieFile];
+
                [nsStringPath release];
+
                [mMovie release];
+
        } else {
+
                NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:QTMovieFlatten];
+
                NSString *tempMovie = [NSString stringWithUTF8String: tmpnam (nil)];
+
// NSLog (@"tempMovie %@\n", tempMovie);
+
// NSLog (@"audioFileName %@\n", audioFileName);
+
                // Write QT movie to a temp file.
+
                [mMovie writeToFile:tempMovie withAttributes:dict];
+
                [mMovie release];
+
+
                NSFileManager *fileManager = [[NSFileManager alloc] init];
+
                NSError      *error;
+
                // Delete the temp audio file and destination movie file.
+
                [fileManager removeItemAtPath:audioFileName error:&error];
+
                [fileManager removeItemAtPath:nsStringPath error:&error];
+
                // Move the temp file to the destination.
+
                [fileManager moveItemAtPath:tempMovie toPath:nsStringPath error:&error];
+
                [fileManager release];
+
                [audioFileName release];
+
                [nsStringPath release];
+
        }
+
+
        return LXe_OK;
+
 
  }
 
  }

Revision as of 23:09, 10 September 2013

Movie_CocoaQT is a basic example plugin. This wiki page is intended as a walkthrough of the code in order to help you better understand the SDK.

When installed, the Movie_CocoaQT plugin adds Cocoa QuickTime saver utility for movies.

Code Walkthrough

Class Declaration

We want our class to create a movie object, so we inherit from CLxImpl_Movie. Our class includes all the basic methods of a movie class. First, our mov_BeginMovie class initiates the saving and provides the file path and the size of the frame. Next, our mov_SetFrameRate function sets the frame rate. After that, our mov_AddImage appends an image to the movie every time it is called. After that, the mov_AddAudio function adds an audio track to the movie file. Finally, our EndMovie function closes the movies file and cleans up the temp files.

class CCocoaQuickTimeMovie : public CLxImpl_Movie
{
    public:
        LxResult		 mov_BeginMovie (const char*,int, int, int);
        LxResult		 mov_SetFramerate (int);
        LxResult		 mov_AddImage (ILxUnknownID);
        LxResult		 mov_EndMovie (void);
        LxResult		 mov_AddAudio (ILxUnknownID);
                       
        static LXtTagInfoDesc	 descInfo[];
                                                   
        int			 frameRate;
        NSString		*nsStringPath;
        QTMovie			*mMovie;
        NSDictionary		*myDict;
        NSString		*audioFileName;
};

Server Tags

Servers tags are examined when the server is initialized, and give information about the server. This is accomplished here by taking a descinfo[] array and associating the relevant data with the corresponding flags. The tags here indicate that the main class will be a server of the movie type that will have the extension .mov named Express Quicktime.

LXtTagInfoDesc	 CCocoaQuickTimeMovie::descInfo[] = {
       { LXsLOD_CLASSLIST,	LXa_MOVIE         },
       { LXsLOD_DOSPATTERN,	"*.mov"           },
       { LXsSAV_DOSTYPE,	"mov"             },
       { LXsSRV_USERNAME,	"Express Quicktime" },
       { 0 }
};

Initialize

Intialize is called when we add the plugin to modo, and is the utility that exports the server. In this Initialize() we export a server of the movie type dependent on the CCocoaQuickTimeMovie class with the "quicktime" name. The LXx_ADD_SERVER method is simply a wrapper that is identical to normal method of adding a server, with the arguments being (interface_to_be_added, class_you_depend_on, server_name). In this case we add the Movie interface, depend on the CCocoaQuickTimeMove, and name our server "quicktime".

void
initialize ()
{
       LXx_ADD_SERVER (Movie, CCocoaQuickTimeMovie, "quicktime");
}

Helper Functions

Apple uses the NS image format for its movie frames, so we need to have some function to convert our format(CGImage) to NS. This function does exactly that. We call this function in our mov_AddImage function in order to convert the CGImage we have constructed.

static NSImage *
cgImageToNSImage (
        CGImageRef	 image)
{
       ...
}

This function creates a temporary audio file with the audio object. Most importantly, if the function is unable to read or write the audio object, it returns an error code. This function is called in mov_AddAudio in order to verify that the audio object passed in is valid.

static LxResult
CreateAudioTempFile (
       NSString		*filePath,
       CLxUser_Audio		*audio)
{
      ...
} 

Implementations

Creates a movie file and starts writing to it.

       LxResult
CCocoaQuickTimeMovie::mov_BeginMovie (
       const char              *fname,
       int                      w,
       int                      h,
       int                      flags)
{
   ...
}	

This function sets the framerate for the movie.

LxResult
CCocoaQuickTimeMovie::mov_SetFramerate (
       int			 frate)
{
      ...
}	

This functions adds another frame to the movie.

LxResult
CCocoaQuickTimeMovie::mov_AddImage (
       ILxUnknownID		 img)
{
      ...
}

This function adds the temp audio file to the movie.

LxResult
CCocoaQuickTimeMovie::mov_AddAudio (
       ILxUnknownID		 au)
{
       ...
}

This function stops the writing to the movie file and cleans up the temp files.

LxResult
CCocoaQuickTimeMovie::mov_EndMovie (void)
{
       ...
}