Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
ConvertTextMovie.c
// ConvertMovie makes a copy of a movie, converting its text tracks to |
// new text tracks with bit map representations of the text. The displayFlags |
// parameter allows one to add new display flags (such as normally time consuming |
// antialias and drop shadow) for the bit map images. The imageTrack parameter |
// indicates whether to save an image of the entire text track or just the |
// text box (as specified by the defaultTextBox field of the text descriptor). |
// The spatial parameter is a pointer to a compression information data structure |
// that specifies how to compress the text bit maps. |
// Note that the resulting movie file (as specified by the dstSpec parameter) will |
// need to be flattened if it contained any non-text tracks in order to make it |
// self-contained. |
#include <QuickDraw.h> |
#include <Files.h> |
#include <StandardFile.h> |
#include <Movies.h> |
#include <ImageCompression.h> |
#include "ConvertTextMovie.h" |
// Special Call to Text Media Handler |
pascal ComponentResult GetMetrics(MediaHandler mh, Handle metrics) |
FIVEWORDINLINE(0x2F3C, 0x04, 0x108, 0x7000, 0xA82A); |
#define FailNil(a) if ((a)==nil) goto bail; |
#define FailOSErr(a) if (err = a) goto bail; |
#define FailMemErr(a) {a; if (err = MemError()) goto bail;} |
#define FailMoviesErr(a) {a; if (err = GetMoviesError()) goto bail;} |
pascal OSErr ConvertAndAddTextTrack(Track srcTrack, Track dstTrack, long newDisplayFlags, Boolean imageTrack, SCSpatialSettings *spatial); |
pascal void ConvertMovie(FSSpec *srcSpec, FSSpec *dstSpec, long newDisplayFlags, Boolean imageTrack, SCSpatialSettings *spatial) |
{ |
OSErr err = 0; |
Movie dstMovie = 0; |
Movie srcMovie = 0; |
Handle trackDataRef = 0; |
Track srcTrack; |
Track dstTrack; |
Handle srcDataRef; |
OSType srcDataRefType; |
long srcDataRefAttributes; |
short resRefNum = 0; |
Fixed width,height; |
OSType mediaType; |
Media srcMedia; |
Media dstMedia; |
TimeValue selectionTime, selectionDuration; |
GWorldPtr gw, movieWorld = 0; |
GDHandle gd; |
short dstRef = 0; |
short srcRef; |
short srcResID; |
short dstResID = 0; |
Rect bounds; |
WindowPtr movieWindow = 0; |
short numTracks, trackNum; |
Point pt; |
GetGWorld(&gw, &gd); |
// Open the source movie file |
FailOSErr( OpenMovieFile(srcSpec, &srcRef, fsRdPerm)); |
srcResID = 0; |
FailOSErr( NewMovieFromFile(&srcMovie, srcRef, &srcResID, 0, 0, 0)); |
CloseMovieFile(srcRef); |
srcRef = 0; |
// Create a new movie file for the destination |
CreateMovieFile(dstSpec, 'TVOD', 0, createMovieFileDeleteCurFile, &dstRef, &dstMovie); |
AddMovieResource(dstMovie, dstRef, &dstResID, 0); |
GetMovieBox(srcMovie, &bounds); |
OffsetRect(&bounds, -bounds.left, -bounds.top); |
SetMovieBox(srcMovie, &bounds); |
// Image the movie into an offscreen buffer |
FailOSErr( NewGWorld(&movieWorld, spatial->depth, &bounds, 0L, 0L, useTempMem)); |
SetMovieGWorld(srcMovie, movieWorld, 0); |
OffsetRect(&bounds, 100, 100); |
// Create a new window to which we can copy the movie image during the conversion |
movieWindow = NewCWindow(0, &bounds, (StringPtr) "\pConverting...", true, noGrowDocProc, (WindowPtr)-1, false, 0); |
AlignWindow(movieWindow, false, &bounds, nil); |
SetMovieActive(srcMovie, true); |
SetPort((GrafPtr)movieWindow); |
// Iterate through the tracks, copying non-text tracks and converting text tracks |
numTracks = GetMovieTrackCount(srcMovie); |
for (trackNum = 1; trackNum <= numTracks; trackNum++) { |
ClearMoviesStickyError(); |
srcTrack = GetMovieIndTrack(srcMovie, trackNum); |
srcMedia = GetTrackMedia(srcTrack); |
srcDataRef = nil; |
GetTrackDimensions(srcTrack, &width, &height); |
dstTrack = NewMovieTrack(dstMovie, width, height, GetTrackVolume(srcTrack)); |
GetMediaHandlerDescription(srcMedia, &mediaType, 0L, 0L); |
if (mediaType == 'text') { |
// TEXT TRACK; CONVERT IT TO BITMAP ("BURNT TEXT") REPRESENTATION |
if (err = ConvertAndAddTextTrack(srcTrack, dstTrack, newDisplayFlags, imageTrack, spatial)) { |
//DebugStr((StringPtr) "\pError returned by ConvertAndAddTextTrack"); |
goto bail; |
} |
} else { |
// NON-TEXT TRACK; COPY A REFERENCE |
// Obtain the data ref of the source media since we are making a reference copy of the track. |
// The user will need to flatten the resulting file if they want a stand alone movie file. |
err = GetMediaDataRef(srcMedia, 1, &srcDataRef, &srcDataRefType, &srcDataRefAttributes); |
dstMedia = NewTrackMedia( dstTrack, mediaType, |
GetMediaTimeScale(srcMedia), srcDataRef, srcDataRefType); |
selectionTime = 0; |
selectionDuration = GetMovieDuration(srcMovie); |
InsertTrackSegment(srcTrack, dstTrack, selectionTime, selectionDuration, 0); |
CopyTrackSettings(srcTrack, dstTrack); |
DisposeHandle(srcDataRef); |
srcDataRef = 0; |
} |
if (err = GetMoviesStickyError()) goto bail; |
} |
UpdateMovieResource(dstMovie, dstRef, dstResID, 0); |
err = GetMoviesError(); |
bail: |
// Clean up |
SetGWorld(gw, gd); |
if (movieWorld) DisposeGWorld(movieWorld); |
if (srcRef) CloseMovieFile(srcRef); |
if (dstRef) CloseMovieFile(dstRef); |
if (srcMovie) DisposeMovie(srcMovie); |
if (dstMovie) DisposeMovie(dstMovie); |
if (movieWindow) DisposeWindow(movieWindow); |
} |
#define min(x,y) ((x) < (y)) ? (x) : (y) |
#define dfDontHilite (1L << 30) |
#define dfDontBitmap (1L << 29) |
pascal OSErr ConvertAndAddTextTrack(Track srcTrack, Track dstTrack, long newDisplayFlags, Boolean imageTrack, SCSpatialSettings *spatial) |
{ |
OSErr err; |
Movie srcMovie = GetTrackMovie(srcTrack); |
Media textMedia; |
MediaHandler textMediaHandler; |
Media newTextMedia; |
short sampleFlags; |
Fixed width, height; |
RgnHandle trackRgn; |
long size, dataSize, newDataSize; |
TimeValue sampleTime, sampleDuration, thisTrackTime, nextTrackTime, interestingDuration, mediaTime; |
ImageDescriptionHandle idh = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription)); |
Handle dataOut = NewHandle(4); |
TextDescriptionHandle tdh = (TextDescriptionHandle) NewHandleClear(sizeof(TextDescription)); |
Ptr imageData = 0; |
Ptr descPtr; |
Ptr dataPtr; |
long descIndex; |
Handle metrics = NewHandle(0); |
GWorldPtr movieGW; |
GDHandle movieGD; |
GWorldPtr saveGW; |
GDHandle saveGD; |
Rect trackRect; |
Rect textBox; |
long compressedDataSize; |
Rect movieBox; |
WindowPtr movieWindow = 0; |
MatrixRecord trackMatrix, movieMatrix; |
long textLen; |
GetPort((GrafPtr*)&movieWindow); |
GetMovieBox(srcMovie, &movieBox); |
// Create new text media |
textMedia = GetTrackMedia(srcTrack); |
textMediaHandler = GetMediaHandler(textMedia); |
FailMoviesErr( newTextMedia = NewTrackMedia(dstTrack, 'text', GetMediaTimeScale(textMedia), 0L, 0L)); |
// Copy info from source track |
err = CopyTrackSettings(srcTrack, dstTrack); |
FailMoviesErr( SetTrackLayer(dstTrack, GetTrackLayer(srcTrack))); |
// Find the first text sample |
thisTrackTime = GetTrackOffset(srcTrack); |
GetTrackNextInterestingTime(srcTrack, nextTimeMediaSample+nextTimeEdgeOK, thisTrackTime, kFix1, |
&nextTrackTime, &interestingDuration); |
SetMovieTimeValue(srcMovie, thisTrackTime); |
// Get the track bounds and set up for compressing text image |
trackRgn = GetTrackSegmentDisplayBoundsRgn(srcTrack, 0, GetTrackDuration(srcTrack)); |
if (!trackRgn || EmptyRgn(trackRgn)) { |
err = -1; |
goto bail; |
} |
trackRect = (*trackRgn)->rgnBBox; |
GetMovieGWorld(srcMovie, &movieGW, &movieGD); |
FailOSErr( GetMaxCompressionSize(movieGW->portPixMap, &trackRect, spatial->depth, spatial->spatialQuality, |
spatial->codecType, 0, &size)); |
FailMemErr( imageData = NewPtr(size)); |
// Get track matrix so that when we compress the image we are looking |
// in the right place in the offscreen buffer |
GetMovieMatrix(srcMovie, &movieMatrix); |
GetTrackMatrix(srcTrack, &trackMatrix); |
ConcatMatrix(&movieMatrix, &trackMatrix); |
FailOSErr( BeginMediaEdits(newTextMedia)); |
while (thisTrackTime >= 0) { |
TimeValue dur; |
short addFlags = 0; |
// Get the current sample |
mediaTime = TrackTimeToMediaTime(thisTrackTime, srcTrack); |
FailOSErr( GetMediaSample(textMedia, dataOut, 0, &dataSize, mediaTime, &sampleTime, &sampleDuration, |
(SampleDescriptionHandle) tdh, &descIndex, 0, 0, &sampleFlags)); |
// Add in the new display flags (and set special dontHilite & dontBitmap flags) |
(*tdh)->displayFlags |= (newDisplayFlags + dfDontHilite + dfDontBitmap); |
err = SetMediaSampleDescription(textMedia, descIndex, (SampleDescriptionHandle) tdh); |
SetMovieTimeValue(srcMovie, thisTrackTime); |
newDataSize = dataSize; |
// Get the track bounds for this time; if imageTrack is true we will compress the entire track image |
// otherwise we just image that portion of the track specified by the defaultTextBox field |
DisposeRgn(trackRgn); |
trackRgn = GetTrackDisplayBoundsRgn(srcTrack); // rgn could change over time |
trackRect = (*trackRgn)->rgnBBox; |
if (imageTrack) { |
textBox = trackRect; |
OffsetRect(&textBox, -trackRect.left, -trackRect.top); |
(*tdh)->defaultTextBox = textBox; |
} else { |
textBox = (*tdh)->defaultTextBox; |
// fix up textBox so it doesn't extend beyond the track |
if (textBox.right > trackRect.right-trackRect.left) |
textBox.right = trackRect.right-trackRect.left; |
if (textBox.bottom > trackRect.bottom-trackRect.top) |
textBox.bottom = trackRect.bottom-trackRect.top; |
(*tdh)->defaultTextBox = textBox; |
} |
// Non-sync samples are hilite samples; we don't need to convert them |
if (sampleFlags & mediaSampleNotSync) { |
addFlags = mediaSampleNotSync; |
goto skipConversion; |
} |
// Force update |
MoviesTask(srcMovie, 0); |
MoviesTask(srcMovie, 0); |
// Copy bits to window (so the user sees something happening) |
CopyBits((BitMap*)&movieGW->portPixMap, (BitMap*)&movieWindow->portBits, &movieBox, &movieBox, srcCopy, 0); |
// Get text metrics data structure for current time (used by text media handler for hiliting) |
// This will be added to the text sample below |
FailOSErr( GetMetrics(textMediaHandler, metrics)); |
// Use track matrix to find text box's location within the movie box |
TransformRect(&trackMatrix, &textBox, 0); |
// Safety code: Force text box to be within the bounds of the track |
textBox.right = min(textBox.right, trackRect.right); |
textBox.bottom = min(textBox.bottom, trackRect.bottom); |
// More safety code: Don't allow text box height to be too small |
if ((textBox.bottom - textBox.top) <= 4) |
textBox.bottom = trackRect.bottom; |
// Compress the text image |
GetGWorld(&saveGW, &saveGD); |
SetGWorld(movieGW, movieGD); |
ForeColor(blackColor); |
BackColor(whiteColor); |
FailOSErr( FCompressImage(movieGW->portPixMap, &textBox, spatial->depth, spatial->spatialQuality, spatial->codecType, 0, 0, 0, 0, 0, 0, idh, imageData)); |
SetGWorld(saveGW, saveGD); |
/********************************************************************/ |
/* Build the new text descriptor */ |
/********************************************************************/ |
// If defaultFontName isn't there then add one |
if ((long)&(*tdh)->defaultFontName - (long)(*tdh) >= (*tdh)->size) { |
short fontNumber = (*tdh)->defaultStyle.scrpFont; |
Str255 fontName; |
fontName[0] = 0; |
// fontNum of 0 or 1 is special case (sys or app font) -> Don't use name |
if (fontNumber != 0 && fontNumber != 1) |
GetFontName(fontNumber, fontName); |
FailMemErr( SetHandleSize((Handle)tdh, sizeof(TextDescription) + fontName[0])); |
BlockMove(fontName, (*tdh)->defaultFontName, fontName[0]+1); |
// Don't use sizeof(TextDescription) to set size, since it pads the length to an even byte |
(*tdh)->size = ((long)&(*tdh)->defaultFontName - (long)(*tdh) + 1) + fontName[0]; |
} |
// increase text description handle size to account for size of the image descriptor + atom header bytes (8) |
FailMemErr( SetHandleSize((Handle)tdh, (*tdh)->size + (*idh)->idSize + 8)); |
(*tdh)->size = (*tdh)->size + (*idh)->idSize + 8; |
HLock((Handle)tdh); |
// Point past the default font name |
descPtr = (Ptr)((long)&(*tdh)->defaultFontName + (*tdh)->defaultFontName[0] + 1); |
// image descriptor atom header |
*(long*)descPtr = (*idh)->idSize + 8; |
descPtr += 4; |
*(long*)descPtr = 'idsc'; |
descPtr += 4; |
// copy image descriptor |
compressedDataSize = (*idh)->dataSize; |
(*idh)->dataSize = 0; // set to zero so that all image descs will be same (field is not used at decompress) |
BlockMove(*idh, descPtr, (*idh)->idSize); |
HUnlock((Handle)tdh); |
/********************************************************************/ |
/* Build the new text sample */ |
/********************************************************************/ |
// Look for and delete any existing bitmap or metric data from existing sample |
textLen = *((short*) *dataOut); |
if (dataSize > (textLen + 2)) { |
// We got extra stuff |
Ptr dataPtr = (*dataOut) + textLen + 2; |
long dataLen; |
do { |
dataLen = *((long*) dataPtr); |
if (dataLen <= 0 || dataLen > dataSize) break; // safety check |
if (((*((long*) (dataPtr+4))) == 'imag') || ((*((long*) (dataPtr+4))) == 'metr')) { |
// Delete the atom by sliding the remaining data over to fill in the gap |
long restOfDataSize = dataSize - (dataPtr+dataLen - *dataOut); |
if (restOfDataSize > 0) |
BlockMove(dataPtr+dataLen, dataPtr, restOfDataSize); |
dataSize -= dataLen; |
} else |
dataPtr += dataLen; |
} while ((dataPtr - *dataOut) < dataSize); |
} |
// Add Compressed Image to SampleData |
newDataSize = dataSize + compressedDataSize + 16 + GetHandleSize(metrics); // 16 = size of 2 atom headers |
FailMemErr( SetHandleSize(dataOut, newDataSize)); |
HLock(dataOut); |
dataPtr = *dataOut+dataSize; |
// 'imag' atom header |
*(long*)dataPtr = compressedDataSize + 16; |
dataPtr += 4; |
*(long*)dataPtr = 'imag'; |
dataPtr += 4; |
// image data atom header |
*(long*)dataPtr = compressedDataSize + 8; |
dataPtr += 4; |
*(long*)dataPtr = 'idat'; |
dataPtr += 4; |
// copy image data |
BlockMove(imageData, dataPtr, compressedDataSize); |
// copy text metrics |
BlockMove(*metrics, dataPtr+compressedDataSize, GetHandleSize(metrics)); |
HUnlock(dataOut); |
skipConversion: |
// Turn off the don't hilite flag |
(*tdh)->displayFlags &= ~dfDontHilite; |
(*tdh)->displayFlags &= ~dfDontBitmap; |
// Add the new sample data to new media |
FailOSErr( AddMediaSample(newTextMedia, dataOut, 0, newDataSize, sampleDuration, |
(SampleDescriptionHandle) tdh, 1, addFlags, &sampleTime)); |
// Insert media into same location (thisTrackTime) in new track |
if (interestingDuration < 0 || interestingDuration > sampleDuration) interestingDuration = sampleDuration; |
dur = GetTrackDuration(dstTrack); |
FailOSErr( InsertMediaIntoTrack(dstTrack, thisTrackTime, sampleTime, interestingDuration, kFix1)); |
dur = GetTrackDuration(dstTrack); |
// Get time for next text sample |
GetTrackNextInterestingTime(srcTrack, nextTimeMediaSample, thisTrackTime, kFix1, &nextTrackTime, &interestingDuration); |
if (thisTrackTime == nextTrackTime) { |
//DebugStr((StringPtr)"\pGetTrackNextInterestingTime returned same time"); |
goto bail; |
} |
thisTrackTime = nextTrackTime; |
} |
err = EndMediaEdits(newTextMedia); |
bail: |
DisposeHandle(dataOut); |
DisposeHandle((Handle)idh); |
DisposeHandle((Handle)tdh); |
DisposeHandle(metrics); |
if (imageData) DisposePtr(imageData); |
if (trackRgn) DisposeRgn(trackRgn); |
return err; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14