//
//  FILE:       MyMovieText.m
//  PROJECT:    iView
//
//  Created by wilson on Fri Aug 27 2004.
//

#import "MyMovieText.h"


@implementation MyMovieText

#define BailErr(x) {err = x; if(err != noErr) return err;}

#define kMyErrorCode		-22222
#define kCaptionDuration	1			// duration of caption in seconds
#define kInvalidFileRefNum	-1			// an invalid file reference number
#define kGetFirstMovie		0
#define kGetFirstTrack		1
#define kTextTrackHeight	20			// default height for text track (in pixels)
#define kTrackOffset		0
#define kMediaOffset		0

/***************************************************************************
*
* FUNCTION: addCaptionToMovie
* PURPOSE : Calls approriate functions to 
*           1. Open a movie
*           2. Create a text track for adding captions
*           3. Close the movie when done
*
****************************************************************************/
-(OSErr)addCaptionToMovie:(NSMutableArray *)theCaption filename:(NSString *)movieFilename
{
    Movie theMovie;
    short myRefNum = kInvalidFileRefNum;
    OSErr err;

    if(!movieFilename)
       NSLog(@"movieFilename is nil");
    
    // Open the movie specified by 'movieFilename'
    err = [self myOpenMovie:&theMovie filename:movieFilename refNum:&myRefNum];
    [self checkError:err message:@"myOpenMovie failed"];

    if (err == noErr)
    {
       // Create a text track for captions
       err = [self myCreateVideoTrack:theMovie caption:theCaption];
       [self checkError:err message:@"myCreateVideoTrack failed"];
    }

    // Done with adding captions, close the movie
    err = [self myCloseMovie:&theMovie refNum:myRefNum];
    [self checkError:err message:@"myCloseMovie failed"];

    return err;
}

/*****************************************************************************
*
* FUNCTION: myOpenMovie
* PURPOSE : Opens a movie and handles the errors accordingly
*
******************************************************************************/
-(OSErr)myOpenMovie:(Movie *)theMovie filename:(NSString *)movieFilename refNum:(short *)theRefNum
{
    MyVideo *myVideo;
    FSSpec myFSSpec;
    OSErr err;
    short myResID = movieInDataForkResID;

    myVideo = [[MyVideo alloc] init];
    if (!myVideo)
    {
       NSRunAlertPanel(@"Error", @"MyVideo not created", @"OK", nil, nil);
       BailErr(kMyErrorCode);
    }

    // Convert the filename from an NSString to a FSSpec structure
    // because this structure is required by OpenMovieFile
    err = [myVideo myMakeFSSpec:&myFSSpec fromPath:movieFilename];
    [self checkError:err message:@"myMakeFSSpec failed"];
    BailErr(err);

    // Open file with exclusive read & write permission
    err = OpenMovieFile(&myFSSpec, theRefNum, fsRdWrPerm);
    [self checkError:err message:@"OpenMovieFile failed"];
    BailErr(err);

    // Create a new movie from the movie file we just opened
    err = NewMovieFromFile(theMovie, *theRefNum, &myResID, NULL, newMovieActive, NULL);
    [self checkError:err message:@"NewMovieFromFile failed"];
    BailErr(err);

    return err;
}

/*****************************************************************************
*
* FUNCTION: myCloseMovie
* PURPOSE : Updates and closes the movie, frees any allocated resources
*
******************************************************************************/
-(OSErr)myCloseMovie:(Movie *)theMovie refNum:(short)theRefNum
{
    short resId = movieInDataForkResID;
    OSErr err = noErr;

    if (theRefNum != kInvalidFileRefNum)
    {
       // We've added captions to the movie that we just opened
       // so update it or the captions won't appear in the movie
       err = UpdateMovieResource(*theMovie, theRefNum, resId, nil);
       [self checkError:err message:@"AddMovieResource failed"];

       // Close the movie file
       err = CloseMovieFile(theRefNum);
       [self checkError:err message:@"CloseMovieFile failed"];

       // Free resources held by the movie
       // If DisposeMovie() is not called, the application may cause error 
       // when trying to overwrite an existing file
       DisposeMovie(*theMovie);
       err = GetMoviesError();
       [self checkError:err message:@"DisposeMovie failed"];
    }

    return err;
}
    
/*****************************************************************************
*
* FUNCTION: myCreateVideoTrack
* PURPOSE : Creates a text track and adds text samples to a movie
* INPUT   : 1. theMovie		- The movie for which the text track is created
*			2. theCaption   - Contains the text samples to be added to the movie
* NOTE    : This function is based on qttext, which can be found at
*			http://developer.apple.com/samplecode/qttext/qttext.html
*
**********************************************************************************/
-(OSErr)myCreateVideoTrack:(Movie)theMovie caption:(NSMutableArray *)theCaption
{
    OSErr 		err = noErr;
    Track 		myVideoTrack = NULL;
    Track		myTextTrack = NULL;
    Media 		myMedia = NULL;
    Fixed 		myWidth;
    Fixed 		myHeight;
    TimeScale 		myTimeScale;
    MatrixRecord	myMatrix;
    
    // Get the first track of the type Video
    myVideoTrack = GetMovieIndTrackType(theMovie,
										kGetFirstTrack,		// Get the first track 
										VideoMediaType,		// of the type VIDEO
										movieTrackMediaType);	// look for media type only
    if (myVideoTrack == NULL)
		goto bail;
	
    GetTrackDimensions(myVideoTrack, &myWidth, &myHeight);
    myTimeScale = GetMediaTimeScale(GetTrackMedia(myVideoTrack));
	
	
    /***********************************************
    *
    *  Create the text track and media
    *
    ************************************************/
    
    myTextTrack = NewMovieTrack(theMovie, myWidth, FixRatio(kTextTrackHeight, 1), kNoVolume);
    if (myTextTrack == NULL)
		goto bail;
	
    myMedia = NewTrackMedia(myTextTrack, TextMediaType, myTimeScale, NULL, 0);
    if (myMedia == NULL)
		goto bail;
	
    // Figure out the text track geometry
    GetTrackMatrix(myTextTrack, &myMatrix);
    TranslateMatrix(&myMatrix, 0, myHeight);
	
    SetTrackMatrix(myTextTrack, &myMatrix);
    SetTrackEnabled(myTextTrack, TRUE);
	
    // We're gonna start editing the media now
    err = BeginMediaEdits(myMedia);
    
    if (err == noErr)
    {
		Rect 		myBounds;
		
		myBounds.top = 0;
		myBounds.left = 0;
		myBounds.right = Fix2Long(myWidth);
		myBounds.bottom = Fix2Long(myHeight);
		
		unsigned arrayLength = 0;
		unsigned i;
		
		arrayLength = [theCaption count];
		for (i = 0; i < arrayLength; i++)
		{
			TimeValue		myTextSampleDuration;
			NSTimeInterval	pauseDuration = 0.0;
			
			UserText *textData = (UserText *)[theCaption objectAtIndex:i];
			pauseDuration = [textData duration] - (NSTimeInterval)kCaptionDuration;
            
            // If there's no text from user, 
            // we add empty strings to the caption
			if (pauseDuration > 0)
			{
                // Calculate the sample duration for the caption
				myTextSampleDuration = (TimeValue)(pauseDuration * (NSTimeInterval)myTimeScale);

				// Add a text containing only spaces to create the effect of pauses
				[self myAddTextSample:&theMovie media:&myMedia bounds:&myBounds
					   text:@" " duration:myTextSampleDuration];
			}
			
			myTextSampleDuration = kCaptionDuration * myTimeScale;

			// Add user-typed text into movie
			[self myAddTextSample:&theMovie media:&myMedia bounds:&myBounds
					text:[textData text] duration:myTextSampleDuration];
		
		}			
    }
	
	// We're done with media editing
    err = EndMediaEdits(myMedia);
    [self checkError:err message:@"EndMediaEdits failed"];
    if (err != noErr)
		goto bail;
	
    // Insert text media into the text track
    err = InsertMediaIntoTrack(myTextTrack,
							   kTrackOffset,
							   kMediaOffset,
							   GetMediaDuration(myMedia),
							   fixed1);
    [self checkError:err message:@"InsertMediaIntoTrack failed"];
    if (err != noErr)
		goto bail;
	
bail:	
		
		return err;
}

/*****************************************************************************
*
* FUNCTION: myAddTextSample
* PURPOSE : Adds a text sample to the media
*
******************************************************************************/
-(OSErr)myAddTextSample:(Movie *)theMovie media:(Media *)theMedia bounds:(Rect *)theBounds
							   text:(NSString *)theText duration:(TimeScale)theDuration 
{
	MediaHandler 	myHandler = NULL;
	TimeRecord		myTimeRecord;
	TimeScale		mediaTimeScale;
	Str255			myPascalStr;
	TimeValue		myTextSampleDuration;
	OSErr			err = noErr;

    myHandler = GetMediaHandler(*theMedia);
    if (myHandler == NULL)
	{
		err = kMyErrorCode;
		goto bail;
	}
	
    // Calculate the sample duration for the text using the media's timescale
	mediaTimeScale = GetMediaTimeScale(*theMedia);
	myTimeRecord.value.lo = theDuration;
	myTimeRecord.value.hi = 0;
	myTimeRecord.scale = GetMovieTimeScale(*theMovie);
	ConvertTimeScale(&myTimeRecord, mediaTimeScale);
	myTextSampleDuration = myTimeRecord.value.lo;
	
    // Convert a CString to a Pascal string
	[self QTText_CopyCStringToPascal:[theText UTF8String] dest:myPascalStr];
	
    // Add the text sample into the media
	err = TextMediaAddTextSample(myHandler,
							  (Ptr)(&myPascalStr[1]),
							  myPascalStr[0],	// first element of a Pascal str specifies its length
							  0,
							  0,
							  0,
							  NULL,
							  NULL,
							  teCenter,
							  theBounds,
							  dfClipToTextBox,
							  0,
							  0,
							  0,
							  NULL,
							  myTextSampleDuration,
							  NULL);
	[self checkError:err message:@"TextMediaAddTextSample failed"];
	
bail:
	return err;
}

//////////
//
// FUNCTION: QTText_CopyCStringToPascal
// PURPOSE : Converts the source C string to a destination Pascal 
//           string as it's copied.
//
// NOTE    : The destination string will be truncated to fit into 
//           a Str255 if necessary. If the C string pointer is NULL, 
//           the Pascal string's length is set to zero.
//
// REFERENCE: This routine is borrowed from CGlue.c, by Nick Kledzik.
//
//////////
-(void)QTText_CopyCStringToPascal:(const char *)theSrc dest:(Str255)theDst
{
   short	myLength = 0;

   // handle case of overlapping strings
   if ((void *)theSrc == (void *)theDst)
   {
      unsigned char		*myCurDst = &theDst[1];
      unsigned char		myChar;
      myChar = *(const unsigned char *)theSrc++;
      while (myChar != '\0')
      {
         unsigned char	myNextChar;
         // use myNextChar so we don't overwrite what we are about to read
         myNextChar = *(const unsigned char *)theSrc++;
         *myCurDst++ = myChar;
         myChar = myNextChar;
         if (++myLength >= 255)
            break;
      }
   }
   else if (theSrc != NULL)
   {
      unsigned char		*myCurDst = &theDst[1];
      short 			myOverflow = 255;		// count down, so test it loop is faster
      register char		myTemp;

      // we can't do the K&R C thing of “while (*s++ = *t++)” because it will copy the trailing zero,
      // which might overrun the Pascal buffer; instead, we use a temp variable
      while ((myTemp = *theSrc++) != 0)
      {
         *(char *)myCurDst++ = myTemp;
         if (--myOverflow <= 0)
            break;
      }
      myLength = 255 - myOverflow;
   }
    
    // set the length of the destination Pascal string
    theDst[0] = myLength;
}

/*****************************************************************************
*
* FUNCTION: checkError
* PURPOSE : Reports errors, if any
*
******************************************************************************/
-(void) checkError:(OSErr)error message:(NSString *)msg
{
    if (error != noErr)
    {
       NSLog(@"Error code = %d", error);
       NSRunAlertPanel(@"Error", msg , @"OK", nil, nil);
    }
}

@end
