/*
	File:		SlideShowImport.c
	
	Description: 	Slide Show Importer component routines.

	Author:		Apple Developer Support, original code by Jim Batson of QuickTime engineering

	Copyright: 	© Copyright 2001-2002 Apple Computer, Inc. All rights reserved.
	
	Disclaimer:	IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
				("Apple") in consideration of your agreement to the following terms, and your
				use, installation, modification or redistribution of this Apple software
				constitutes acceptance of these terms.  If you do not agree with these terms,
				please do not use, install, modify or redistribute this Apple software.

				In consideration of your agreement to abide by the following terms, and subject
				to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs
				copyrights in this original Apple software (the "Apple Software"), to use,
				reproduce, modify and redistribute the Apple Software, with or without
				modifications, in source and/or binary forms; provided that if you redistribute
				the Apple Software in its entirety and without modifications, you must retain
				this notice and the following text and disclaimers in all such redistributions of
				the Apple Software.  Neither the name, trademarks, service marks or logos of
				Apple Computer, Inc. may be used to endorse or promote products derived from the
				Apple Software without specific prior written permission from Apple.  Except as
				expressly stated in this notice, no other rights or licenses, express or implied,
				are granted by Apple herein, including but not limited to any patent rights that
				may be infringed by your derivative works or by other works in which the Apple
				Software may be incorporated.

				The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
				WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
				WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
				PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
				COMBINATION WITH YOUR PRODUCTS.

				IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
				CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
				GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
				ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
				OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
				(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
				ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
				
	Change History (most recent first):
	
	   <2>		3/22/02		SRK				Carbonized/Win32
	   <1>	 	4/01/01		Jim Batson		first file
*/

#if __APPLE_CC__
    #include <Carbon/Carbon.h>
    #include <QuickTime/QuickTime.h>
#else
    #include <ImageCodec.h>
    #include <TextUtils.h>
    #include <string.h>
    #include <Files.h>
    #include <Movies.h>
    #include <MediaHandlers.h>
#endif


#include "SlideShowImport.h"
#include "SlideShowImportConstants.h"
#include "SlideShowImportVersion.h"

// ---------------------------------------------------------------------------------
//		¥ ComponentDispatchHelper Definitions ¥
// ---------------------------------------------------------------------------------

#define MOVIEIMPORT_BASENAME()		SlideShowImport
#define MOVIEIMPORT_GLOBALS() 		SlideShowImportGlobals storage

#define CALLCOMPONENT_BASENAME()	MOVIEIMPORT_BASENAME()
#define	CALLCOMPONENT_GLOBALS()		MOVIEIMPORT_GLOBALS()

#define COMPONENT_UPP_PREFIX()		uppMovieImport
#define COMPONENT_DISPATCH_FILE		"SlideShowImportDispatch.h"
#define COMPONENT_SELECT_PREFIX()  	kMovieImport

#include <Components.k.h>				// StdComponent's .k.h

#if __APPLE_CC__
    #include <QuickTime/QuickTimeComponents.k.h>
    #include <QuickTime/ComponentDispatchHelper.c>
#else
    #include <QuickTimeComponents.k.h>
    #include <ComponentDispatchHelper.c>	// make our dispatcher and cando
#endif

// ---------------------------------------------------------------------------------
//		¥ SlideShowImportOpen ¥
//
// Handle the Standard Component Manager Open Request
//
// Set up the components global data storage
// ---------------------------------------------------------------------------------
pascal ComponentResult SlideShowImportOpen(SlideShowImportGlobals store, ComponentInstance self)
{
	ComponentResult err;
	
	// Allocate and initialize all globals to 0 by calling NewPtrClear.
	store = (SlideShowImportGlobals)NewPtrClear(sizeof(SlideShowImportGlobalsRecord));
	err = MemError();
	if (err) goto bail;

	store->self = self;
	
	SetComponentInstanceStorage(self, (Handle)store);

bail:	
	return err;
}

// ---------------------------------------------------------------------------------
//		¥ SlideShowImportClose ¥
//
// Handle the Standard Component Manager Close Request
//
// Clean up the components global data storage
// ---------------------------------------------------------------------------------
pascal ComponentResult SlideShowImportClose(SlideShowImportGlobals store, ComponentInstance self)
{
#pragma unused(self)

	if (store) 
	{
		DisposePtr((Ptr)store);
	}
		
	return noErr;
}

// ---------------------------------------------------------------------------------
//		¥ SlideShowImportVersion ¥
//
// Handle the Standard Component Manager Version Request
//
// Return the components version information
// ---------------------------------------------------------------------------------
pascal long SlideShowImportVersion(SlideShowImportGlobals store)
{
#pragma unused(store)
	return kSlideShowImportVersion;
}

// ---------------------------------------------------------------------------------
//		¥ SlideShowImportFile ¥
//
// Import data from a file
//
// The file in this case will be our XML file, which we will use to construct our movie
// ---------------------------------------------------------------------------------
pascal ComponentResult SlideShowImportFile(SlideShowImportGlobals store,
											const FSSpec 	*theFile,
											Movie 			theMovie,
											Track 			targetTrack,
											Track 			*usedTrack,
											TimeValue 		atTime,
											TimeValue 		*addedDuration,
											long 			inFlags,
											long 			*outFlags)
{
	ComponentResult	err;
	AliasHandle		alias;

	err = QTNewAlias(theFile, &alias, true);
	if (err) goto bail;
	
	err = MovieImportDataRef(store->self,
							(Handle)alias,
							rAliasType,
							theMovie,
							targetTrack,
							usedTrack,
							atTime,
							addedDuration,
							inFlags,
							outFlags);
							
	DisposeHandle((Handle)alias);

bail:
	return err;
}

// ---------------------------------------------------------------------------------
//		¥ SlideShowImportDataRef ¥
//
// Import data from a data reference
//
// The data reference in this case will be our XML data, which we will use to
// construct our movie
// ---------------------------------------------------------------------------------
pascal ComponentResult SlideShowImportDataRef(	SlideShowImportGlobals 	store,
												Handle 					dataRef,
												OSType 					dataRefType,
												Movie 					theMovie,
												Track 					targetTrack,
												Track 					*usedTrack,
												TimeValue 				atTime,
												TimeValue 				*addedDuration,
												long 					inFlags,
												long 					*outFlags
												)
{
#pragma unused(store, usedTrack, atTime, addedDuration, inFlags)

	ComponentResult	err = noErr;
	SlideShowFile	theFile = nil;

	// target tracks not supported, only can import into an entire movie
	if (dataRef == nil || theMovie == nil || targetTrack != nil)
	{
		err = paramErr;
		goto bail;
	}

	if (outFlags)
	{
		*outFlags = 0;
	}
	
	// initialize
	theFile = (SlideShowFile)NewPtrClear(sizeof(SlideShowFileRecord));
	err = MemError();
	if (err) goto bail;
	
	// copy XML source data ref and other information needed during import
	err = HandToHand(&dataRef);
	if (err) goto bail;
	theFile->dataRef = dataRef;
	
	theFile->dataRefType = dataRefType;
	theFile->theMovie = theMovie;

	// parse the SlideShow file
	err	= ReadSlideShowFile(theFile);
	if (err) goto bail;

	// build a movie
	err	= BuildMovie(theFile);
	if (err) goto bail;

bail:
	if (theFile)
		DisposeSlideShowFile(theFile);
		
	return err;
}

// ---------------------------------------------------------------------------------
//		¥ SlideShowImportGetMIMETypeList ¥
//
// Returns a list of MIME types supported by the movie import component.
//
// ---------------------------------------------------------------------------------
pascal ComponentResult SlideShowImportGetMIMETypeList(SlideShowImportGlobals store, QTAtomContainer *mimeInfo)
{
	ComponentResult err = noErr;

	if (mimeInfo == nil)
	{
		err = paramErr;
	}
	else
	{
		err	= GetComponentResource((Component)store->self, kMimeInfoMimeTypeTag, kSlideShowImportThingRes, (Handle *)mimeInfo);
	}
    
    return err;
}

// ---------------------------------------------------------------------------------
//		¥ ReadSlideShowFile ¥
//
// Parse the XML file and get all the slideshow element attributes
// ---------------------------------------------------------------------------------
ComponentResult ReadSlideShowFile(SlideShowFile theFile)
{
	ComponentResult 	err 		= noErr;
	ComponentInstance	xmlParser 	= nil;
	XMLDoc				document 	= nil;
	long				index;
	short				elementType;

	// create a SlideShow parser	
	xmlParser = CreateXMLParserForSlideShow();

	if (xmlParser == nil)
	{
		err = -1;
		goto bail;
	}

	// do the parse
	err = XMLParseDataRef(xmlParser, theFile->dataRef, theFile->dataRefType, 0, &document);

	if (document->rootElement.identifier == element_slideshow && document->rootElement.contents)
	{
		// get the slideshow element attributes
		err = GetRootElementAttributes(document->rootElement.attributes, theFile);
		
		// scan through the contents looking for images 
		for (index = 0; document->rootElement.contents[index].kind != xmlContentTypeInvalid; index++)
		{
			if (document->rootElement.contents[index].kind == xmlContentTypeElement)
			{
				elementType = document->rootElement.contents[index].actualContent.element.identifier;
				
				switch (elementType)
				{
					case element_image:
					{
						// parse the image
						err = ParseAndSaveImage(&(document->rootElement.contents[index].actualContent.element), theFile);
						break;
					}
					case element_effect:
					{
						err = ParseEffectElement(&(document->rootElement.contents[index].actualContent.element), theFile);
						break;
					}
						break;
					case element_audio:
					{
						err = ParseAudioElement(&(document->rootElement.contents[index].actualContent.element), theFile);						
						break;
					}
				}
			}
		}
	}
	else
	{
		err = invalidMovie;
	}
	
bail:
	if (document)
	{
		XMLParseDisposeXMLDoc(xmlParser, document);
	}
	
	if (xmlParser)
	{
		CloseComponent(xmlParser);
	}
	
	return err;
}

// ---------------------------------------------------------------------------------
//		¥ CreateXMLParserForSlideShow ¥
//
// Locate the XML parser component
// Add the specified elements to the parser
// ---------------------------------------------------------------------------------
ComponentInstance CreateXMLParserForSlideShow()
{
	ComponentInstance	xmlParser = OpenDefaultComponent(xmlParseComponentType, xmlParseComponentSubType);
	UInt32				elementID, attributeID;

	if (xmlParser)
	{
		XML_ADD_ELEMENT(slideshow);
			XML_ADD_ATTRIBUTE_AND_VALUE(width, attributeValueKindInteger, nil);
			XML_ADD_ATTRIBUTE_AND_VALUE(height, attributeValueKindInteger, nil);

		XML_ADD_ELEMENT(image);
			XML_ADD_ATTRIBUTE(src);
			XML_ADD_ATTRIBUTE_AND_VALUE(dur, attributeValueKindInteger, nil);

		XML_ADD_ELEMENT(effect);
			XML_ADD_ATTRIBUTE(type);
			XML_ADD_ATTRIBUTE_AND_VALUE(dur, attributeValueKindInteger, nil);

		XML_ADD_ELEMENT(audio);
			XML_ADD_ATTRIBUTE(src);
	}

	return xmlParser;
}

// ---------------------------------------------------------------------------------
//		¥ GetRootElementAttributes ¥
//
// Get the slideshow movie width and height attributes
// ---------------------------------------------------------------------------------
ComponentResult GetRootElementAttributes(XMLAttribute *attributes, SlideShowFile theFile)
{
	ComponentResult err = noErr;
	
	// save root attribute values (both required)
	err = GetIntegerAttribute(attributes, attr_width, &theFile->movieWidth);
	err = GetIntegerAttribute(attributes, attr_height, &theFile->movieHeight);

    return err;
}

// ---------------------------------------------------------------------------------
//		¥ AddSlideShowElement ¥
//
// Add a SlideShowElementRecord record to our queue of elements
// ---------------------------------------------------------------------------------
void AddSlideShowElement(SlideShowFile theFile, SlideShowElement theElement)
{
	if (theFile->elements == nil)
	{
		theFile->elements = theElement;
	}
	else
	{
		SlideShowElement temp = theFile->elements;
		
		while (temp && temp->nextElement)
			temp = (SlideShowElement)(temp->nextElement);
		
		temp->nextElement = theElement;
	}
}

// ---------------------------------------------------------------------------------
//		¥ ParseAndSaveImage ¥
//
//  Build a SlideShowElementRecord record for the current image and add it to our list
// ---------------------------------------------------------------------------------
ComponentResult	ParseAndSaveImage(XMLElement *element, SlideShowFile theFile)
{
	ComponentResult err = noErr;
	XMLAttributePtr	attributes = element->attributes;
	SlideShowElement theTempImage = nil;
	
	// allocate and initialize using NewPtrClear
	theTempImage = (SlideShowElement)NewPtrClear(sizeof(SlideShowElementRecord));
	err = MemError();
	if (err) goto bail;
	
	theTempImage->elementKind = kSlideShowImageKind;
	
	// save image values
	err = GetIntegerAttribute(attributes, attr_dur, &(theTempImage->image.duration));
	if (err) goto bail;
	err = GetStringAttribute(attributes, attr_src, &(theTempImage->image.src));
	if (err) goto bail;

	// add the element to our list
	AddSlideShowElement(theFile, theTempImage);
	
	theFile->numImages++;
	
bail:

	if (err && theTempImage)
	{
		DisposeAllElements(theFile);
	}
	
	return err;
}


// ---------------------------------------------------------------------------------
//		¥ ParseEffectElement ¥
//
//  Build a SlideShowElementRecord record for the current effect and add it to our list
// ---------------------------------------------------------------------------------
ComponentResult	ParseEffectElement(XMLElement *element, SlideShowFile theFile)
{
	ComponentResult 	err 			= noErr;
	XMLAttributePtr		attributes 		= element->attributes;
	SlideShowElement 	theTempEffect 	= nil;
	
	// allocate and initialize using NewPtrClear
	theTempEffect = (SlideShowElement)NewPtrClear(sizeof(SlideShowElementRecord));
	err = MemError();
	if (err) goto bail;
	
	// save image values
	theTempEffect->elementKind = kSlideShowEffectKind;
	
	err = GetIntegerAttribute(attributes, attr_dur, &(theTempEffect->effect.duration));
	if (err) goto bail;
	err = GetStringAttribute(attributes, attr_type, &(theTempEffect->effect.type));
	if (err) goto bail;

	// add the element to our list
	AddSlideShowElement(theFile, theTempEffect);
	
	theFile->numEffects++;
	
bail:
	if (err && theTempEffect)
	{
		DisposeAllElements(theFile);
	}
	
	return err;
}
	

// ---------------------------------------------------------------------------------
//		¥ ParseAudioElement ¥
//
//  Build a SlideShowElementRecord record for the current audio element and add it to
//  our list
// ---------------------------------------------------------------------------------
ComponentResult	ParseAudioElement(XMLElement *element, SlideShowFile theFile)
{
	ComponentResult 	err 			= noErr;
	XMLAttributePtr		attributes 		= element->attributes;
	SlideShowElement	theTempEffect 	= nil;
	
	// allocate and initialize using NewPtrClear
	theTempEffect = (SlideShowElement)NewPtrClear(sizeof(SlideShowElementRecord));
	err = MemError();
	if (err) goto bail;
	
	// save image values
	theTempEffect->elementKind = kSlideShowAudioKind;

	err = GetStringAttribute(attributes, attr_src, &(theTempEffect->movie.src));
	if (err) goto bail;

	// add the element to our list
	AddSlideShowElement(theFile, theTempEffect);
	
	theFile->numAudioTracks++;
	
bail:
	if (err && theTempEffect)
	{
		DisposeAllElements(theFile);
	}
	
	return err;
}



// ---------------------------------------------------------------------------------
//		¥ BuildMovie ¥
//
//	Steps
//	- Add all the images to a single video track
//	- Determine where effects will occur
//	- Effects types are:
//
//		dissolve
//		wipe - direction
//		heart or star-wipe
//		
// - add audio element
// ---------------------------------------------------------------------------------
ComponentResult BuildMovie(SlideShowFile theFile)
{
	ComponentResult		err 		= noErr;
	SlideShowElement 	theElement 	= nil;
	TimeValue 			theTime 	= 0;
	Fixed 				maxWidth 	= 0;
	Fixed 				maxHeight 	= 0;
				
		
		// add the images to a single track -- theFile->theImageTrack
		for (theElement = theFile->elements; theElement; theElement = (SlideShowElement)(theElement->nextElement))
		{
			
			switch (theElement->elementKind)
			{
				case kSlideShowImageKind:
					if (theFile->thePendingEffect)
					{
						err = CreateEffectTrack(theFile, theFile->thePreviousSource, &(theElement->image), theFile->thePendingEffect, &theTime);
						theFile->thePendingEffect = nil;
						theFile->thePreviousSource = &(theElement->image);
						if (err) goto bail;
					}
					else
					{
						// add the image to the movie
						err = AddImageToMovie(theFile, &theElement->image, &theTime);
						theFile->thePreviousSource = &(theElement->image);
						if (err) goto bail;
							
						if (theElement->image.discoveredWidth > maxWidth)
							maxWidth = theElement->image.discoveredWidth;
						if (theElement->image.discoveredWidth > maxHeight)
							maxHeight = theElement->image.discoveredHeight;
					}
					break;
				
				case kSlideShowEffectKind:
				{
					
					// ¥¥¥Êadd the second image plus the effect tracks (2 source + effect)
					
					theFile->thePendingEffect = &(theElement->effect);
					
				}
				break;
				
				case kSlideShowAudioKind:
					break;
			}
		}

		if ((theFile->movieWidth == 0) || (theFile->movieHeight == 0)) 
		{
			Fixed 	oldWidth, oldHeight;
			Track 	nextVideoTrack 	= nil;
			long 	trackIndex 		= 1;
			
			GetTrackDimensions(theFile->theImageTrack, &oldWidth, &oldHeight);
			SetTrackDimensions(theFile->theImageTrack, (0 == theFile->movieWidth) ? maxWidth : oldWidth , (0 == theFile->movieHeight) ? maxHeight : oldHeight);

			// spin over other video tracks and set their dimensions, too
			
			while (nil != (nextVideoTrack = GetMovieIndTrackType(theFile->theMovie, trackIndex++, VideoMediaType, movieTrackMediaType)))
			{
				if (nextVideoTrack != theFile->theImageTrack)
				{
					GetTrackDimensions(theFile->theImageTrack, &oldWidth, &oldHeight);
					SetTrackDimensions(nextVideoTrack, oldWidth, oldHeight);
				}
			}
		}
		
		
		// Now look for audio elements
		for (theElement = theFile->elements; theElement; theElement = (SlideShowElement)(theElement->nextElement))
		{			
			switch (theElement->elementKind)
			{
				case kSlideShowAudioKind:
				{
					TimeValue addedTime = 0;
					
					err = AddAudioToMovie(theFile, &theElement->movie, &addedTime );
					goto bail;
				}
				break;
			}
		}
		
bail:
	return err;
}




// ---------------------------------------------------------------------------------
//		¥ AddImageToMovie ¥
//
// Locate the first track in the movie and add the image to the track's media
// ---------------------------------------------------------------------------------
OSErr AddImageToMovie(SlideShowFile theFile, SlideShowImage theImage, TimeValue *theTime)
{
	ComponentResult 		err 			= noErr;
	ComponentInstance		ci 				= nil;
	Movie					refMovie;
	Track					refTrack;	//, theTrack = nil;
	Media					refMedia;	//, theMedia;
	SampleDescriptionHandle	desc 			= nil;
	long					dataOffset, size;
	OSType					mediaType;
	TimeValue				duration;
	OSType					dataRefType;
	Handle					dataRef 		= nil;
	Ptr						theURL 			= nil;
	short					newDataRefIndex = 0;
	TimeValue				theSampleTime;
	
	// copy the url
	theURL = NewPtr((theImage->src)[0] + 1);
	if (theURL == nil) goto bail;
	BlockMove(theImage->src + 1, theURL, (theImage->src)[0]);
	theURL[(theImage->src)[0]] = '\0';
	
	// get the data ref and try to open the movie
	err = CreateDataRefFromURL(theURL, theFile->dataRef, theFile->dataRefType, &dataRef, &dataRefType);
	err = NewMovieFromDataRef(&refMovie, newMovieDontResolveDataRefs, nil, dataRef, dataRefType);
	if (err || refMovie == nil) goto bail;

	// get the first track's media
	refTrack = GetMovieIndTrack(refMovie, 1);
	refMedia = GetTrackMedia(refTrack);

	// allocate sample description
	desc = (SampleDescriptionHandle)NewHandle(0);
	err = MemError();
	if (err) goto bail;

	// get the ref media sample reference
	err = GetMediaSampleReference(refMedia, &dataOffset, &size, 0, nil, nil, desc, nil, 1, nil, 0);
	GetMediaHandlerDescription(refMedia, &mediaType, nil, nil);
	if (err || mediaType != VideoMediaType) goto bail;

	theImage->discoveredWidth = FixRatio((**(ImageDescriptionHandle)desc).width,1);
	theImage->discoveredHeight = FixRatio((**(ImageDescriptionHandle)desc).height,1);
	
	if (nil == theFile->theImageTrack)
	{
		// create a new track
		if (theFile->movieWidth == 0)
			theFile->movieWidth = theImage->discoveredWidth >> 16;
		if (theFile->movieHeight == 0)
			theFile->movieHeight = theImage->discoveredHeight >> 16;
			
		theFile->theImageTrack = NewMovieTrack(theFile->theMovie, theFile->movieWidth ? (theFile->movieWidth << 16) : theImage->discoveredWidth, 
									theFile->movieHeight ? theFile->movieHeight << 16 : theImage->discoveredHeight, 0);
		if (theFile->theImageTrack == nil) goto bail;
		
		// create a track media
		theFile->theImageMedia = NewTrackMedia(theFile->theImageTrack, VideoMediaType, GetMovieTimeScale(theFile->theMovie), nil, 0);
		if (theFile->theImageMedia == nil) goto bail;
	}
	
	// add data ref to media for this image's file. This may return an already added data ref.
	err = AddMediaDataRef(theFile->theImageMedia, &newDataRefIndex, dataRef, dataRefType);
	if (err) goto bail;
	
	theImage->dataRefIndex = (**desc).dataRefIndex = newDataRefIndex;
	
	// add a media sample reference to our movie
	duration = max(1, theImage->duration * 600);


	// ¥¥ we DO NOT ever write to the track so this is OK
	// by not calling the BeginMediaEdits/EndMediaEdits pair
	// we are adding only references
//	err	= BeginMediaEdits(theMedia);
//	if (err) goto bail;
	err = AddMediaSampleReference(theFile->theImageMedia, dataOffset, size, duration, desc, 1, 0, &theSampleTime);
	if (err) goto bail;
	theImage->sampleTime = theSampleTime;
//	err = EndMediaEdits(theMedia);
//	if (err) goto bail;
//	err	= InsertMediaIntoTrack(theTrack, *theTime, 0, duration, FixDiv(Long2Fix(GetMediaDuration(theMedia)), Long2Fix(duration)));
	err	= InsertMediaIntoTrack(theFile->theImageTrack, *theTime, theSampleTime, duration, fixed1);
	if (err) goto bail;

	// increment our movie time
	*theTime += duration;

bail:
	if (dataRef)
	{
		DisposeHandle(dataRef);
	}
	
	if (desc)
	{
		DisposeHandle((Handle)desc);
	}
	
	if (ci)
	{
		CloseComponent(ci);
	}	
	
	return err;
}


// ---------------------------------------------------------------------------------
//		¥ AddAudioToMovie ¥
//
//  Open our audio source file as a movie, then add the audio track from this source
//  movie to our destination movie.
//
// ---------------------------------------------------------------------------------
OSErr AddAudioToMovie(SlideShowFile theFile, SlideShowMovie theMovie, TimeValue *theTime)
{
	ComponentResult 		err 			= noErr;
	ComponentInstance		ci 				= nil;
	Movie					refMovie;
	SampleDescriptionHandle	desc 			= nil;
	OSType					dataRefType;
	Handle					dataRef 		= nil;
	Ptr						theURL 			= nil;
	
	// copy the url
	theURL = NewPtr((theMovie->src)[0] + 1);
	if (theURL == nil) goto bail;
	BlockMove(theMovie->src + 1, theURL, (theMovie->src)[0]);
	theURL[(theMovie->src)[0]] = '\0';
	
	// get the data ref and try to open the movie
	err = CreateDataRefFromURL(theURL, theFile->dataRef, theFile->dataRefType, &dataRef, &dataRefType);
	err = NewMovieFromDataRef(&refMovie, newMovieDontResolveDataRefs, nil, dataRef, dataRefType);
	if (err || refMovie == nil) goto bail;

	// add the movie to the track
	if (theMovie->dontScaleImagesAndEffectsToThisMovie)
	{
		SetMovieSelection(refMovie, 0, GetMovieDuration(refMovie));
		AddMovieSelection(theFile->theMovie, refMovie);
	}
	else
	{
		ScaleMovieSegment(theFile->theMovie, 0, GetMovieDuration(theFile->theMovie), GetMovieDuration(refMovie));
		SetMovieSelection(refMovie, 0, GetMovieDuration(refMovie));
		AddMovieSelection(theFile->theMovie, refMovie);
	}
	
	// increment our movie time
	*theTime = 0;

bail:
	if (dataRef)
	{
		DisposeHandle(dataRef);
	}
	
	if (desc)
	{
		DisposeHandle((Handle)desc);
	}
	
	if (ci)
	{
		CloseComponent(ci);
	}	
	
	return err;
}


// ---------------------------------------------------------------------------------
//		¥ CreateDataRefFromURL ¥
//
//  Create a URL data reference
//
// ---------------------------------------------------------------------------------

OSErr CreateDataRefFromURL(char* theURL, Handle baseDRef, OSType baseDRefType, Handle *dref, OSType *drefType)
{
	ComponentInstance	ci 				= 0;
	Handle				absoluteDRef 	= NULL;
	OSErr				err				= noErr;

	*dref = NULL;
	
	err	= PtrToHand(theURL, dref, (SInt32)(strlen(theURL) + 1));
	if (err) goto Bail;

	*drefType = URLDataHandlerSubType;

	if (OpenAComponent(GetDataHandler(baseDRef, baseDRefType, kDataHCanRead), &ci) != noErr)
		goto Bail;

	if (DataHSetDataRefWithAnchor(ci, baseDRef, URLDataHandlerSubType, *dref) != noErr)
		goto Bail;

	if (DataHGetDataRefAsType(ci, baseDRefType, &absoluteDRef) != noErr)
		goto Bail;

	DisposeHandle(*dref);
	*dref = absoluteDRef;
	*drefType = baseDRefType;

Bail:
	if (NULL != ci)
	{
		CloseComponent(ci);
	}
	
	return	err;
}

// ---------------------------------------------------------------------------------
//		¥ DisposeSlideShowFile ¥
//
// Delete the slide show file object and all its elements
// ---------------------------------------------------------------------------------
void DisposeSlideShowFile(SlideShowFile theFile)
{
	// delete the entire element list
	DisposeAllElements(theFile);

	if (theFile->dataRef)
		DisposeHandle(theFile->dataRef);
		
	// delete the file object
	DisposePtr((Ptr)theFile);
}


// ---------------------------------------------------------------------------------
//		¥ DisposeElementFields ¥
//
// Dispose the image and effect element fields
// ---------------------------------------------------------------------------------
void DisposeElementFields(SlideShowElement element)
{
	if (element)
	{
		switch (element->elementKind)
		{
			case kSlideShowImageKind:
			{
				DisposePtr((Ptr)element->image.src);
				element->image.src = nil;
			}
			break;
			
			case kSlideShowEffectKind:
			{
				DisposePtr((Ptr)element->effect.type);
				element->effect.type = nil;
				
				if (element->effect.effectDescription)
					QTDisposeAtomContainer(element->effect.effectDescription);
				element->effect.effectDescription = nil;
			}
			break;
		}
	}
}
	

// ---------------------------------------------------------------------------------
//		¥ DisposeAllElements ¥
//
// Dispose all elements from the slide show file
// ---------------------------------------------------------------------------------
void DisposeAllElements(SlideShowFile theFile)
{
	if (theFile->elements)
	{
		SlideShowElement element, temp;
		
		element = theFile->elements;
		
		// delete all the elements
		while (element)
		{
			temp = element;
			
			element = (SlideShowElement)(element->nextElement);
			
			DisposeElementFields(temp);
			DisposePtr((Ptr)temp);
		}
		
		theFile->elements = nil;
	}
}
	
// ---------------------------------------------------------------------------------
//		¥ GetStringAttribute ¥
//
// Get a string attribute
// ---------------------------------------------------------------------------------
ComponentResult GetStringAttribute(XMLAttributePtr attributes, short attributeType, StringPtr *theString)
{
	ComponentResult err = noErr;
	long 			attributeIndex, stringLength;
	
	// get the attribute index in the array
	attributeIndex = GetAttributeIndex(attributes, attributeType);
	
	// get the value
	if (attributeIndex != attributeNotFound && (attributes[attributeIndex].valueKind == attributeValueKindCharString))
	{
		// allocate the string
		stringLength = strlen((Ptr)(attributes[attributeIndex].valueStr));
		*theString = (StringPtr)NewPtr(stringLength + 1);
		err = MemError();
		if (err) goto bail;

		// copy the string value
		BlockMove(attributes[attributeIndex].valueStr, *theString + 1, stringLength);
		(*theString)[0] = stringLength;
	}
	else
	{
		err = attributeNotFound;
	}
bail:
	
	return err;
}

// ---------------------------------------------------------------------------------
//		¥ GetIntegerAttribute ¥
//
// Get the integer attribute
// ---------------------------------------------------------------------------------
ComponentResult GetIntegerAttribute(XMLAttributePtr attributes, short attributeType, long *theNumber)
{
	ComponentResult err = noErr;
	long 			attributeIndex;
	
	// get the attribute index in the array
	attributeIndex = GetAttributeIndex(attributes, attributeType);
	
	// get the value
	if (attributeIndex != attributeNotFound && (attributes[attributeIndex].valueKind & attributeValueKindInteger) && theNumber)
	{
		*theNumber = attributes[attributeIndex].value.number;
	}
	else
	{
		err = attributeNotFound;
	}
	
	return err;
}
	
// ---------------------------------------------------------------------------------
//		¥ GetAttributeIndex ¥
//
// Get the index of the attribute
// ---------------------------------------------------------------------------------
long GetAttributeIndex(XMLAttributePtr attributes, short attributeType)
{
	long index = 0;

	if (!attributes)
	{
		index = attributeNotFound;
		goto bail;
	}

	while ((attributes[index]).identifier != xmlIdentifierInvalid && (attributes[index]).identifier != attributeType)
	{
		index++;
	}

	if ((attributes[index]).identifier == xmlIdentifierInvalid)
	{
		index = attributeNotFound;
	}

bail:
	
	return index;
}

// ---------------------------------------------------------------------------------
//		¥ max ¥
// ---------------------------------------------------------------------------------
long max(long num1, long num2)
{
	if (num1 > num2)
	{
		return num1;
	}
	else
	{
		return num2;
	}
}

// ---------------------------------------------------------------------------------
//		¥ CreateEffectTrack ¥
//
// Given two sources and an effect type, create an effects track
// ---------------------------------------------------------------------------------
ComponentResult CreateEffectTrack(SlideShowFile theFile, SlideShowImage sourceA, SlideShowImage sourceB, SlideShowEffect effect, TimeValue *theTime)
{
	ComponentResult			err 					= noErr;
	Track					sourceATrack 			= nil, sourceBTrack = nil, theEffectTrack = nil;
	Media					theEffectMedia 			= nil;
	OSType					effectCode 				= 0;
	SMPTEWipeType			wipeType 				= 0;
	QTAtomContainer 		theEffectDescription 	= nil, inputMap = nil;
	Handle					dataRef 				= nil;
	ImageDescriptionHandle	desc 					= nil;
	QTAtom					inputAtom;
	long 					name, refIndex;
	OSType					inputType,myType;
	TimeValue				effectStart 			= *theTime;
	
	// add the effect time
	*theTime += effect->duration * 600;

	// add the image
	err = AddImageToMovie(theFile, sourceB, theTime);
	if (err) goto bail;

	// initialize
	dataRef = CreateHandleDataRef();
	if (dataRef == nil) goto bail;
	err = QTNewAtomContainer(&theEffectDescription);
	if (err) goto bail;
	err = QTNewAtomContainer(&inputMap);
	if (err) goto bail;
	desc = (ImageDescriptionHandle)NewHandle(0);
	if (noErr != (err = MemError())) goto bail;
	effectCode = GetEffectType(effect->type, &wipeType);

	// create two tracks that reference the appropriate portions of the source and dest tracks
	err = CreateImageTrackForEffect(theFile, effectStart, effect->duration * 600, sourceA->dataRefIndex, &sourceATrack, sourceA->sampleTime);
	if (err || (sourceATrack == nil)) goto bail;
	err = CreateImageTrackForEffect(theFile, effectStart, effect->duration * 600, sourceB->dataRefIndex, &sourceBTrack, sourceB->sampleTime);
	if (err || (sourceBTrack == nil)) goto bail;

	// create the effects track and media
	theEffectTrack = NewMovieTrack(theFile->theMovie, theFile->movieWidth << 16, theFile->movieHeight << 16, 0);
	if (theEffectTrack == nil) goto bail;
	theEffectMedia = NewTrackMedia(theEffectTrack, VideoMediaType, GetMovieTimeScale(theFile->theMovie), dataRef, HandleDataHandlerSubType);
	if (theEffectMedia == nil) goto bail;

	// create an effect description and set the height and width
	err = MakeImageDescriptionForEffect(effectCode, &desc);
	if (err) goto bail;
	(*desc)->width = theFile->movieWidth;
	(*desc)->height = theFile->movieHeight;

	// add the required atoms, sources and the effect type
	myType = EndianU32_NtoB(effectCode);
	QTInsertChild(theEffectDescription, kParentAtomIsContainer, kParameterWhatName, kParameterWhatID, 0, sizeof(myType), &myType, nil);
	
	if (wipeType)
	{
		myType = EndianU32_NtoB(wipeType);
		QTInsertChild(theEffectDescription, kParentAtomIsContainer, 'wpID', 1, 0, sizeof(myType), &myType, nil);
	}
	
	name = EndianU32_NtoB('src1');
	QTInsertChild(theEffectDescription, kParentAtomIsContainer, kEffectSourceName, 1, 0, sizeof(name), &name, nil);
	name = EndianU32_NtoB('src2');
	QTInsertChild(theEffectDescription, kParentAtomIsContainer, kEffectSourceName, 2, 0, sizeof(name), &name, nil);

	// add effects sample to movie
	err = BeginMediaEdits(theEffectMedia);
	if (err) goto bail;
	err = AddMediaSample(theEffectMedia, theEffectDescription, 0, GetHandleSize(theEffectDescription), effect->duration * 600, (SampleDescriptionHandle)desc, 1, 0, nil);
	if (err) goto bail;
	err = EndMediaEdits(theEffectMedia);
	if (err) goto bail;
	err = InsertMediaIntoTrack(theEffectTrack, *theTime - (effect->duration + sourceB->duration) * 600, 0, effect->duration * 600, fixed1);
	if (err) goto bail;

	// first input
	AddTrackReference(theEffectTrack, sourceATrack, kTrackModifierReference, &refIndex);
	QTInsertChild(inputMap, kParentAtomIsContainer, kTrackModifierInput, refIndex, 0, 0, nil, &inputAtom);
	inputType = EndianU32_NtoB(kTrackModifierTypeImage);
	QTInsertChild(inputMap, inputAtom, kTrackModifierType, 1, 0, sizeof(inputType), &inputType, nil);
	name = EndianU32_NtoB('src1');
	QTInsertChild(inputMap, inputAtom, kEffectDataSourceType, 1, 0, sizeof(name), &name, nil);

	// second input
	AddTrackReference(theEffectTrack, sourceBTrack, kTrackModifierReference, &refIndex);
	QTInsertChild(inputMap, kParentAtomIsContainer, kTrackModifierInput, refIndex, 0, 0, nil, &inputAtom);
	QTInsertChild(inputMap, inputAtom, kTrackModifierType, 1, 0, sizeof(inputType), &inputType, nil);
	name = EndianU32_NtoB('src2');
	QTInsertChild(inputMap, inputAtom, kEffectDataSourceType, 1, 0, sizeof(name), &name, nil);

	// set that map
	SetMediaInputMap(theEffectMedia, inputMap);
	
	// set and update the layer
	SetTrackLayer(theEffectTrack, -1);

bail:
	if (dataRef)
	{
		DisposeHandle(dataRef);
	}
	
	if (inputMap)
	{
		QTDisposeAtomContainer(inputMap);
	}
	
	if (theEffectDescription)
	{
		QTDisposeAtomContainer(theEffectDescription);
	}
	
	if (desc)
	{
		DisposeHandle((Handle)desc);
	}
	
	return err;
}

// ---------------------------------------------------------------------------------
//		¥ CreateImageTrackForEffect ¥
//
// Create a new track for the effect 
// ---------------------------------------------------------------------------------
ComponentResult	CreateImageTrackForEffect(SlideShowFile 	theFile,
											TimeValue 		effectOffset,
											TimeValue 		duration,
											long 			dataRefIndex,
											Track 			*sourceTrack,
											TimeValue 		sampleTime)
{
	ComponentResult			err 			= noErr;
	Media 					sourceMedia;
	TimeValue				theSampleTime;
	Handle					dataRef 		= nil;
	OSType					dataRefType;
	short					newDataRefIndex = 0;
	long					dataOffset, size;
	ImageDescriptionHandle	desc 			= nil;

	// initialize
	desc = (ImageDescriptionHandle)NewHandle(0);
	if (noErr != (err = MemError())) goto bail;
	
	// create a new track and media
	*sourceTrack = NewMovieTrack(theFile->theMovie, theFile->movieWidth << 16, theFile->movieHeight << 16, 0);
	if (*sourceTrack == nil) goto bail;
	sourceMedia = NewTrackMedia(*sourceTrack, VideoMediaType, GetMovieTimeScale(theFile->theMovie), nil, 0);
	if (sourceMedia == nil) goto bail;

	// get and add the dataref from the image track
	err = GetMediaDataRef(theFile->theImageMedia, dataRefIndex, &dataRef, &dataRefType, nil);
	if (err) goto bail;
	err = AddMediaDataRef(sourceMedia, &newDataRefIndex, dataRef, dataRefType);
	if (err) goto bail;

	// add a sample reference
	err = GetMediaSampleReference(theFile->theImageMedia, &dataOffset, &size, sampleTime, nil, nil, (SampleDescriptionHandle)desc, nil, 1, nil, 0);
	if (err) goto bail;
	(**desc).dataRefIndex = newDataRefIndex;
	err = AddMediaSampleReference(sourceMedia, dataOffset, size, duration, (SampleDescriptionHandle)desc, 1, 0, &theSampleTime);
	if (err) goto bail;

	// insert into track
	err	= InsertMediaIntoTrack(*sourceTrack, effectOffset, theSampleTime, duration, fixed1);
	if (err) goto bail;

bail:
	if (dataRef)
	{
		DisposeHandle(dataRef);
	}
	
	if (desc)
	{
		DisposeHandle((Handle)desc);
	}
	
	return err;
}

// ---------------------------------------------------------------------------------
//		¥ CreateHandleDataRef ¥
//
// Make a handle data reference
// ---------------------------------------------------------------------------------
Handle CreateHandleDataRef()
{
	ComponentResult	err 	= noErr;
	Handle 			dataRef = nil;
	long			dataAtom[2];
	
	dataRef	= NewHandleClear(sizeof(Handle) + sizeof(char));
	
	if (dataRef)
	{
		dataAtom[0]	= EndianU32_NtoB(sizeof(long) + sizeof(OSType));
		dataAtom[1]	= EndianU32_NtoB(FOUR_CHAR_CODE('data'));
		
		err	= PtrAndHand(dataAtom, dataRef, sizeof(long) + sizeof(OSType));
		
		if (err)
		{
			DisposeHandle(dataRef);
			dataRef = nil;
		}
	}
	
	return dataRef;
}

// ---------------------------------------------------------------------------------
//		¥ GetEffectType ¥
//
// Given an effects type string, return the corresponding effects constant 
// ---------------------------------------------------------------------------------
OSType GetEffectType(StringPtr theEffect, SMPTEWipeType *theWipeType)
{
	OSType effectType = kSlideTransitionType;
	long wipeType = 0;
	
	if (EqualString(theEffect, "\pdissolve", false, false))
		effectType = kCrossFadeTransitionType;
	else if (EqualString(theEffect, "\pwipe", false, false))
		effectType = kSlideTransitionType;
	else if (EqualString(theEffect, "\pheart", false, false))
	{
		effectType = kWipeTransitionType;
		wipeType = kHeartWipe;
	}
	else if (EqualString(theEffect, "\pstar", false, false))
	{
		effectType = kWipeTransitionType;
		wipeType = kFivePointStarWipe;
	}

	*theWipeType = wipeType;
	
	return effectType;
}

// ---------------------------------------------------------------------------------
//		¥ SlideShowImportRegister ¥
//
// Register our component manually, when running in linked mode
// ---------------------------------------------------------------------------------
#ifndef STAND_ALONE

extern void MySlideShowImportRegister(void);

void MySlideShowImportRegister(void)
{
	ComponentDescription	theComponentDescription;
	Handle					nameHdl;
	#if !TARGET_API_MAC_CARBON
		ComponentRoutineUPP componentEntryPoint = NewComponentRoutineProc(SlideShowImportComponentDispatch);
	#else
		ComponentRoutineUPP componentEntryPoint = NewComponentRoutineUPP((ComponentRoutineProcPtr)SlideShowImportComponentDispatch);
	#endif

	PtrToHand("\pSlideShow", &nameHdl, 8);
  	theComponentDescription.componentType = MovieImportType;
  	theComponentDescription.componentSubType = FOUR_CHAR_CODE('QTSL');
  	theComponentDescription.componentManufacturer = kAppleManufacturer;
  	theComponentDescription.componentFlags = canMovieImportFiles | canMovieImportInPlace | hasMovieImportMIMEList | canMovieImportDataReferences;
  	theComponentDescription.componentFlagsMask = 0;

	RegisterComponent(&theComponentDescription, componentEntryPoint, 0, nameHdl, 0, 0);
}

#endif