/* Copyright (c) 2004, Noah Sorscher
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 	¥ 	Redistributions of source code must retain the above copyright
 * 	notice, this list of conditions and the following disclaimer.
 * 	¥ 	Redistributions in binary form must reproduce the above copyright
 * 	notice, this list of conditions and the following disclaimer in the
 * 	documentation and/or other materials provided with the distribution.
 * 	¥ 	Neither the name of the Noah Sorscher nor the names of its
 * 	contributors may be used to endorse or promote products derived from
 * 	this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
//
//  MHMovie.m
//  Hootenanny
//
//  Created by Noah Sorscher on Tue Mar 25 2003.
//  Copyright (c) 2003 __MyCompanyName__. All rights reserved.
//

#import <QTKit/QTKit.h>
#import "MHMovie.h"
#import "MHChapters.h"
#import "APIC.h"
#import <QuickTime/QuickTimeComponents.h>
#import "NSStringWebAdditions.h"

#define DUMMYURL [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"dummy" ofType:@"txt"]]
#define DEBUG NO


long long Wide2LongLong(wide w);

char *rstring(char *s, int length);


@implementation MHTrack
- (id) initWithTrack:(Track)aTrack
{
    if (!aTrack) {
        [self release];
        return nil;
    }
    myTrack = aTrack;
    return [super init];
}

- (MHTrack *)alternate
{
    Track t = GetTrackAlternate(myTrack);
    if (t == myTrack) return nil;
    return [[[MHTrack alloc] initWithTrack:t] autorelease];
}

- (BOOL) containsString:(NSString *)searchString
{
    if (![[self type] isEqualToString:@"Text"]) return NO;
    TimeValue tv = 0;
    Handle h = NewHandleClear(0);
    GetTrackNextInterestingTime(myTrack, nextTimeMediaSample + nextTimeEdgeOK, 0, fixed1, &tv, NULL);
    while (tv != -1) {
        GetMediaSample(GetTrackMedia(myTrack), h, 0, NULL, TrackTimeToMediaTime(tv, myTrack), NULL, NULL, NULL, NULL, 0, NULL, NULL);
        short size = *(short *)(*h);
        NSString *text = [NSString stringWithCString:(char *)(*h + sizeof(short)) length:size];
        if (text && [text rangeOfString:searchString options:NSCaseInsensitiveSearch].location != NSNotFound) return YES;
        GetTrackNextInterestingTime(myTrack, nextTimeMediaSample, tv, fixed1, &tv, NULL);
    }
    return NO;
}

- (NSString *)description
{
    NSString *type = [self type];
    if (![type isEqualToString:@"Text"]) return type;
    NSMutableString *string = [NSMutableString stringWithString:@""];
    TimeValue tv = 0;
    Handle h = NewHandleClear(0);
    short flags = nextTimeMediaSample + nextTimeEdgeOK;
    while (YES) {
        GetTrackNextInterestingTime(myTrack, flags, tv, fixed1, &tv, NULL);
        if (tv == -1) break;
        flags = nextTimeMediaSample;
        GetMediaSample(GetTrackMedia(myTrack), h, 0, NULL, TrackTimeToMediaTime(tv, myTrack), NULL, NULL, NULL, NULL, 0, NULL, NULL);
        short size = *(short *)(*h);
        NSString *line = [NSString stringWithCString:(char *)(*h + sizeof(short)) length:size];
        if (line && [[line whitespaceTrimmed] length]) {
            if ([string length]) [string appendString:@"|"];
            [string appendString:line];
        }
    }
    return string;
}

- (NSRect)displayFrame
{
    Fixed w, h;
    GetTrackDimensions(myTrack, &w, &h);
    Rect box;
    box.top = box.left = 0;
    box.right = Fix2Long(w);
    box.bottom = Fix2Long(h);
    MatrixRecord m;
    GetTrackMatrix(myTrack, &m);
    TransformRect(&m, &box, NULL);
    NSRect rect;
    rect.origin.x = box.left;
    rect.origin.y = box.bottom;
    rect.size.width = box.right - box.left;
    rect.size.height = box.bottom - box.top;
    return rect;
}

- (TimeValue)duration
{
    return GetTrackDuration(myTrack);
}

- (BOOL)enabled
{
    return GetTrackEnabled(myTrack);
}

- (NSString *)format
{
    ImageDescriptionHandle h = (ImageDescriptionHandle)NewHandle(sizeof(SoundDescription));
    GetMediaSampleDescription(GetTrackMedia(myTrack), 1, (SampleDescriptionHandle)h);
    OSType t;
    GetMediaHandlerDescription(GetTrackMedia(myTrack), &t, 0, 0);
    switch (t) {
        case VideoMediaType: return [NSString stringWithCString:(char *)&(*h)->name + 1 length:*(char *)&(*h)->name];
        default: break;
    }
    return nil;
}

- (NSString *)fourCC
{
    SampleDescriptionHandle h = (SampleDescriptionHandle)NewHandle(sizeof(SoundDescription));
    GetMediaSampleDescription(GetTrackMedia(myTrack), 1, h);
    long type = (*(SoundDescriptionHandle)h)->dataFormat;
    if (!type) type = (*(ImageDescriptionHandle)h)->cType;
    if (type) {
        char *buffer = (char *)&type;
        NSString *output = [NSString stringWithCString:buffer];
        if ([output length] > 4) output = [output substringToIndex:4];
        if ([output hasPrefix:@"ms"]) {
            output = [NSString stringWithFormat:@"ms\\%d\\%d", (int)buffer[2], (int)buffer[3]];
            if (buffer[2] == 0 && buffer[3] == 85) output = [output stringByAppendingString:@" (mp3)"];
        }
        return output;
    }
    return nil;
}

- (BOOL)highQuality
{
    long hints = hintsHighQuality;
    GetMediaPlayHints(GetTrackMedia(myTrack), &hints);
    long hints2 = hints & hintsHighQuality;             // if I just return this, it always evaluates to false for some unknown reason.  does this mean my compiler is smoking crack?
    return hints2 > 0;
}

- (NSString *)language
{
    short lang = GetMediaLanguage(GetTrackMedia(myTrack));
    return [tLanguages numberToString:lang];
}

- (short)layer
{
    return GetTrackLayer(myTrack);
}

- (double) mbSize
{
    wide w;
    OSErr err = GetTrackDataSize64(myTrack, 0, GetTrackDuration(myTrack), &w);
    if (err) NSLog(@"error getting track data size: %d", err);
    return (double)Wide2LongLong(w) / 1024.0 / 1024.0;
}

- (NSString *)name
{
    NSString *name = nil;
    Handle h = NewHandleClear(0);
    GetUserData(GetTrackUserData(myTrack), h, kUserDataName, 1);
    int size = GetHandleSize(h);
    if (size) {
        char *buffer = (char *)*h;
        name = [NSString stringWithCString:buffer length:size];
    }
    return name;
}

- (TimeValue)offset
{
    return GetTrackOffset(myTrack);
}

- (BOOL)preloads
{
	long preloadFlags, defaultHints;
	TimeValue start, duration;
	GetTrackLoadSettings(myTrack, &start, &duration, &preloadFlags, &defaultHints);
	return (preloadFlags & preloadAlways || preloadFlags & preloadOnlyIfEnabled);
}

- (Track)QTTrack
{
    return myTrack;
}

- (void)setAlternate:(MHTrack *)track
{
    if (track) SetTrackAlternate(myTrack, [track QTTrack]);
}

- (void)setDisplayFrame:(NSRect)frame
{
	NSRect oldFrame = [self displayFrame];
	MatrixRecord m;
	GetTrackMatrix(myTrack, &m);
	int dx = frame.origin.x - oldFrame.origin.x;
	int dy = frame.origin.y - oldFrame.origin.y;
	if (dx || dy) {
		Fixed deltaH = Long2Fix(dx);
		Fixed deltaV = Long2Fix(dy);
		TranslateMatrix(&m, deltaH, deltaV);
	}
	float dw = frame.size.width - oldFrame.size.width;
	float dh = frame.size.height - oldFrame.size.height;
	if (dw || dh) {
		Fixed scaleX = FixRatio(frame.size.width, oldFrame.size.width);
		Fixed scaleY = FixRatio(frame.size.height, oldFrame.size.height);
		ScaleMatrix(&m, scaleX, scaleY, Long2Fix(0), Long2Fix(0));
	}
	SetTrackMatrix(myTrack, &m);
}

- (void)setDuration:(TimeValue)duration
{
    OSErr err = ScaleTrackSegment(myTrack, GetTrackOffset(myTrack), GetTrackDuration(myTrack), duration);
    if (err) NSLog(@"error scaling track segment: %d", err);
}

- (void)setEnabled:(BOOL)flag
{
    SetTrackEnabled(myTrack, flag);
}

- (void)setFormat:(NSString *)format
{
    if ([format length] > 31) format = [format substringToIndex:31];
    ImageDescriptionHandle h = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
    GetMediaSampleDescription(GetTrackMedia(myTrack), 1, (SampleDescriptionHandle)h);
    CopyCStringToPascal([format cString], (*h)->name);
    SetMediaSampleDescription(GetTrackMedia(myTrack), 1, (SampleDescriptionHandle)h);
}

- (void)setFourCC:(NSString *)fourcc
{
    if (!fourcc) return;
    char buffer[] = {0, 0, 0, 0};
    NSScanner *scan = [NSScanner scannerWithString:fourcc];
    int spot = 0;
    while (![scan isAtEnd] && spot < 4) {
        if ([scan scanString:@"\\" intoString:nil]) {
            int n = -1;
            [scan scanInt:&n];
            if (n != -1)
                buffer[spot++] = n;
        } else {
            buffer[spot++] = [fourcc characterAtIndex:[scan scanLocation]];
            [scan setScanLocation:[scan scanLocation]+1];
        }
    }
    OSType type = 0;
    type += buffer[0] * 256 * 256 * 256;
    type += buffer[1] * 256 * 256;
    type += buffer[2] * 256;
    type += buffer[3];
    Media media = GetTrackMedia(myTrack);
    SampleDescriptionHandle desc = (SampleDescriptionHandle)NewHandle(sizeof(SampleDescription));
    GetMediaSampleDescription(media, 1, desc);
    MoveHHi((Handle)desc);
    OSType trackType;
    GetMediaHandlerDescription(media, &trackType, 0, 0);
    switch (trackType) {
        case VideoMediaType: case MPEGMediaType: (*(ImageDescriptionHandle)desc)->cType = type; break;
        case SoundMediaType: default: (*desc)->dataFormat = type; break;
    }
    SetMediaSampleDescription(media, 1, desc);
}

- (void)setHighQuality:(BOOL)flag
{
    long hint = 0;
    if (flag) hint = hintsHighQuality;
    SetMediaPlayHints(GetTrackMedia(myTrack), hint, hintsHighQuality);
}

- (BOOL)setLanguage:(NSString *)lang
{
    short newLang = [tLanguages stringToNumber:lang];
    if (newLang == -1) return NO;
    SetMediaLanguage(GetTrackMedia(myTrack), newLang);
    return YES;
}

- (void)setLayer:(short)layer
{
    SetTrackLayer(myTrack, layer);
}

- (void)setName:(NSString *)name
{
    if (!name || ![name isKindOfClass:[NSString class]]) return;
    char *buffer = malloc(sizeof(char) * ([name length] + 1));
    [name getCString:buffer];
    SetUserDataItem(GetTrackUserData(myTrack), buffer, [name length], kUserDataName, 1);
    free(buffer);
}

- (void)setOffset:(TimeValue)offset
{
    SetTrackOffset(myTrack, offset);
}

- (void)setPreloads:(BOOL)p
{
	if (p)
		SetTrackLoadSettings(myTrack, -1, 0, preloadAlways, 0);
	else
		SetTrackLoadSettings(myTrack, -1, 0, 0, 0);
}		

- (void)setTimeScale:(TimeScale)ts
{
    SetMediaTimeScale(GetTrackMedia(myTrack), ts);
}

- (void)setVendor:(NSString *)ven
{
    if (!ven) return;
    char buffer[] = {0, 0, 0, 0};
    NSScanner *scan = [NSScanner scannerWithString:ven];
    int spot = 0;
    while (![scan isAtEnd] && spot < 4) {
        if ([scan scanString:@"\\" intoString:nil]) {
            int n = -1;
            [scan scanInt:&n];
            if (n != -1)
                buffer[spot++] = n;
        } else {
            buffer[spot++] = [ven characterAtIndex:[scan scanLocation]];
            [scan setScanLocation:[scan scanLocation]+1];
        }
    }
    OSType type = 0;
    type += buffer[0] * 256 * 256 * 256;
    type += buffer[1] * 256 * 256;
    type += buffer[2] * 256;
    type += buffer[3];
    Media media = GetTrackMedia(myTrack);
    SampleDescriptionHandle desc = (SampleDescriptionHandle)NewHandle(sizeof(SampleDescription));
    GetMediaSampleDescription(media, 1, desc);
    MoveHHi((Handle)desc);
    OSType trackType;
    GetMediaHandlerDescription(media, &trackType, 0, 0);
    switch (trackType) {
        case VideoMediaType: case MPEGMediaType: (*(ImageDescriptionHandle)desc)->vendor = type; break;
        //case SoundMediaType: default: (*desc)->dataFormat = type; break;
    }
    SetMediaSampleDescription(media, 1, desc);
}

- (NSString *)type
{
    OSType t;
    GetMediaHandlerDescription(GetTrackMedia(myTrack), &t, 0, 0);
    char *buffer;
    switch (t) {
        case SoundMediaType: return @"Sound";
        case VideoMediaType: return @"Video";
        case SpriteMediaType: return @"Sprite";
        case MPEGMediaType: return @"MPEG";
        case TextMediaType: return @"Text";
        case TimeCodeMediaType: return @"TimeCode";
        default:
            buffer = (char *)&t;
            return [NSString stringWithCString:buffer length:4];
    }
}

- (BOOL) isSoundType
{
    OSType t;
    GetMediaHandlerDescription(GetTrackMedia(myTrack), &t, 0, 0);
    return (SoundMediaType == t);
}

- (MHTrack *)chapterTrack{
    Track cTrack = GetTrackReference(myTrack, kTrackReferenceChapterList, 1);
    if (cTrack)
        return [[[MHTrack alloc] initWithTrack:cTrack] autorelease];
    return nil;
}

- (void)remove
{
    // first delete any references to the track
    Movie mov = GetTrackMovie(myTrack);
    long i, n = GetMovieTrackCount(mov);
    for (i=1;i<=n;i++) {
        Track track = GetMovieIndTrack(mov, i);
        if (track && track != myTrack) {
            OSType type = 0L;
            type = GetNextTrackReferenceType(track, type);
            while (type != 0L) {
                long j, refCount = GetTrackReferenceCount(track, type);
                for (j = refCount; j >= 1; j--) {
                    Track aRefTrack = GetTrackReference(track, type, j);
                    if (aRefTrack == myTrack)
                        DeleteTrackReference(track, type, j);
                }
                type = GetNextTrackReferenceType(track, type);
            }
        }
    }
    DisposeMovieTrack(myTrack);
    myTrack = nil;
}

- (TimeScale)timeScale
{
    return GetMediaTimeScale(GetTrackMedia(myTrack));
}

- (NSString *)vendor
{
    SampleDescriptionHandle h = (SampleDescriptionHandle)NewHandle(sizeof(SoundDescription));
    GetMediaSampleDescription(GetTrackMedia(myTrack), 1, h);
    long type = (*(ImageDescriptionHandle)h)->vendor;
    if (!type) type = (*(SoundDescriptionHandle)h)->vendor;
    if (type) {
        char *buffer = (char *)&type;
        NSString *output = [NSString stringWithCString:buffer length:4];
        //if ([output length] > 4) output = [output substringToIndex:4];
        if ([output hasPrefix:@"ms"]) {
            output = [NSString stringWithFormat:@"ms\\%d\\%d", (int)buffer[2], (int)buffer[3]];
            //if (buffer[2] == 0 && buffer[3] == 85) output = [output stringByAppendingString:@" (mp3)"];
        }
        return output;
    }
    return nil;
}

@end

@implementation MHMovie


//void CopyCStringToPascal (const char *theSrc, Str31 theDst);

BOOL asks = NO;
BOOL ignores = YES;
NSArray *languages = nil;

static OSType theKUserDataTypes[] = {kUserDataTextAlbum , kUserDataTextArtist , kUserDataTextAuthor , kUserDataTextChapter , kUserDataTextComment , kUserDataTextComposer , kUserDataTextCopyright , kUserDataTextCreationDate , kUserDataTextDescription , kUserDataTextDirector , kUserDataTextDisclaimer , kUserDataTextEncodedBy , kUserDataTextFullName , kUserDataTextGenre , kUserDataTextHostComputer , kUserDataTextInformation , kUserDataTextKeywords , kUserDataTextMake , kUserDataTextModel , kUserDataTextOriginalArtist , kUserDataTextOriginalFormat , kUserDataTextOriginalSource , kUserDataTextPerformers , kUserDataTextProducer , kUserDataTextProduct , kUserDataTextSoftware , kUserDataTextSpecialPlaybackRequirements , kUserDataTextTrack , kUserDataTextWarning , kUserDataTextWriter , kUserDataTextURLLink , kUserDataTextEditDate1, '\251web', '\251rat'};

static BOOL WriteMovieToPath(Movie movie, NSString *path, BOOL flatten);
Track GetBiggestTrack(Movie qtMovie, OSType type);

+ (NSArray *)supportedMetadata
{
    return [NSArray arrayWithObjects: @"Album", @"Artist", @"Author", @"Chapter", @"Comment", @"Composer", @"Copyright", @"Creation Date", @"Description", @"Director", @"Disclaimer", @"Encoded By", @"Full Name", @"Genre", @"Host Computer", @"Information", @"Keywords", @"Make", @"Model", @"Original Artist", @"Original Format", @"Original Source", @"Performers", @"Producer", @"Product", @"Software", @"Special Playback Requirements", @"Track", @"Warning", @"Writer", @"URL Link", @"Edit Date", @"Website", @"Rating", nil];

}

+ (void)setAsksForMissingFiles:(BOOL)flag
{
    asks = flag;
}

+ (void)setIgnoresMissingFiles:(BOOL)flag
{
    ignores = flag;
}

+ (void)setLanguages:(NSArray *)langs
{
    languages = [[NSArray alloc] initWithArray:langs];
}

- (id)qtKitMovie
{
	return mainMovie;
}

- (Movie)quickTimeMovie
{
	return [self QTMovie];
}

- (MHMovie *)initWithURL:(NSURL *)aURL byReference:(BOOL)ref
{
    //NSLog(@"MHMovie initWithURL:byReference");
    //self = [super initWithURL:DUMMYURL byReference:YES];
    isOffline = NO;
	wasOffline = NO;
    saveInPlaceUnSafe = NO;
    listNumbers = nil;
    _sortedKeys = nil;
	//myQTKitMovie = nil;
	statusString = nil;
    //previewPic = nil;
    if ([aURL isFileURL])
        stringPath = [aURL path];
    else
        stringPath = [aURL absoluteString];
    [stringPath retain];
	NSError *err = nil;
	mainMovie = [[QTMovie movieWithFile:stringPath error:&err] retain];
	if (err) NSLog(@"error opening movie at %@: %@", stringPath, err);
	myRefMovie = nil; //[MHUtil uniquePath:[[MHUtil supportDir] stringByAppendingPathComponent:[stringPath lastPathComponent]]];
	//[mainMovie writeToFile:myRefMovie withAttributes:nil];
	err = nil;
	//mainMovie = [[QTMovie movieWithFile:myRefMovie error:&err] retain];
	//if (err) NSLog(@"error opening reference movie at %@: %@", myRefMovie, err);
	[mainMovie setDelegate:self];
    //mainMovie = [[NSMovie alloc] initWithMovie:[myQTKitMovie quickTimeMovie]];
    if (nil == mainMovie || nil == self) {
        [self release];
        return nil;
    }
    data = [[NSMutableDictionary alloc] init];
    metadata = [[NSMutableDictionary alloc] init];
    info = [[NSMutableDictionary alloc] init];
    tempFiles = nil;
    isChanged = chaptersNeedUpdating = NO;
    //NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];
    Chapters = [[MHChapters alloc] initFromMovie:self];
    //[pool2 release];
    Menus = nil;
    [data setObject:stringPath forKey:@"File Path"];
    NSString *filename = [stringPath lastPathComponent];
    //if (![filename hasSuffix:@".mov"]) filename = [filename stringByAppendingString:@".mov"];
    [info setObject:filename forKey:@"File Name"];
    [self readMetadata];
    [self gatherData];
    if (![self objectForKey:@"Full Name"])
        [self setObject:[self stringValue] forKey:@"Full Name"];
    
    return self;
}

- (MHMovie *)initWithURL:(NSURL *)aURL
{
    return (MHMovie *)[self initWithURL:aURL byReference:NO];
}

- (MHMovie *)initWithPath:(NSString *)path
{
    NSURL *aURL;
    if ([path hasPrefix:@"/"])
        aURL = [NSURL fileURLWithPath:path];
    else
        aURL = [NSURL URLWithString:path];
    return [self initWithURL:aURL];
    // open movie at path
}

- (MHMovie *)initWithDefaultsEntry:(NSDictionary *)entry
{
    //NSLog(@"MHMovie initWithDefaultsEntry");
    //NSURL *aURL;
    isOffline = NO;
	wasOffline = YES;
    saveInPlaceUnSafe = NO;
    listNumbers = nil;
    _sortedKeys = nil;
    mainMovie = nil;
	statusString = nil;
    //mainQTMovie = nil;
    data = [[NSMutableDictionary alloc] init];
    metadata = [[NSMutableDictionary alloc] init];
    info = [[NSMutableDictionary alloc] init];
    //previewPic = nil;
    stringPath = [entry objectForKey:@"File Path"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:stringPath]) {
        NSData *fsrefD = [entry objectForKey:@"FSRef"];
        if (fsrefD) {
            UInt8 *newPath = malloc(2048 * sizeof(char));
            FSRef *ref = (FSRef *)[fsrefD bytes];
            OSErr err = FSRefMakePath(ref, newPath, 2048);
            if (!err && newPath) {
                stringPath = [NSString stringWithUTF8String:newPath];
                [data setObject:stringPath forKey:@"File Path"];
            }
            free (newPath);
        }
    }
    [stringPath retain];
	myRefMovie = nil; //[entry objectForKey:@"myRefMovie"];
	//if (!myRefMovie) myRefMovie = [[MHUtil uniquePath:[[MHUtil supportDir] stringByAppendingPathComponent:[entry objectForKey:@"File Name"]]] retain];
    if (![[NSFileManager defaultManager] fileExistsAtPath:stringPath]) {
        if (ignores) {
            [self release];
            return nil;
        }
        if (asks && [self askForMovie]) {
        } else {
            stringPath = [[[NSBundle mainBundle] pathForResource:@"dummy" ofType:@"txt"] retain];
            isOffline = YES;
        }
    }
    
    tempFiles = [entry objectForKey:@"tempFiles"];
    if (tempFiles)
        tempFiles = [NSMutableArray arrayWithArray:tempFiles];
    isChanged = chaptersNeedUpdating = NO;
        
    // open metadata and file data
    
    NSArray *bad = [NSArray arrayWithObjects:@"File Path", @"chapters", @"Seconds Elapsed", @"Seconds", @"Movie Length", @"File Size", @"Original Height", @"Saved Height", @"Original Width", @"Saved Width", @"FPS", @"Video Format", @"Video FourCC", @"Video Vendor", @"Video Width", @"Video Height", @"Video Bitrate", @"Audio Channels", @"Audio Format", @"Audio Sample Rate", @"Audio Bitrate", @"otherLangs", @"menus", @"Cover Image", @"File Name", @"File Creation Date", @"File Modification Date", @"Source Name", @"Timecode", @"isChanged", nil];
    //[metadata addEntriesFromDictionary:entry];
    int i, n = [[entry allKeys] count];
    for (i=0;i<n;i++) {
        NSString *key = [[entry allKeys] objectAtIndex:i];
        id value = [entry objectForKey:key];
        if (![bad containsObject:key]) {
            if ([value isKindOfClass:[NSString class]])
                [metadata setObject:[NSMutableDictionary dictionaryWithObject:value forKey:key] forKey:@"English"];
            else if ([value isKindOfClass:[NSDictionary class]])
                [metadata setObject:[NSMutableDictionary dictionaryWithDictionary:value] forKey:key];
        }
    }
    NSArray *infos = [NSArray arrayWithObjects:@"File Name", nil];
    NSArray *nots = [NSArray arrayWithObjects:@"chapters", @"otherLangs", @"isChanged", nil];
    n = [bad count];
    for (i=0;i<n;i++) {
        NSString *key = [bad objectAtIndex:i];
        id value = [entry objectForKey:key];
        if (value) {
            if ([infos containsObject:key] && ![info objectForKey:key])
                [info setObject:value forKey:key];
            else if (![nots containsObject:key] && ![data objectForKey:key])
                [data setObject:value forKey:key];
        }
    }
    if ([[entry objectForKey:@"isChanged"] boolValue]) isChanged = YES;
    [data removeObjectForKey:@"Cover Image"];
//    NSString *cover = [entry objectForKey:@"Cover Image"];
//    if (cover) [self setImageFile:cover];
    
    Chapters = [[MHChapters alloc] init];
    NSArray *chapArray = [entry objectForKey:@"chapters"];
    if (chapArray) {
        [Chapters addObjectsFromArray:chapArray];
        if ([[entry objectForKey:@"multiChaps"] boolValue])
            //[Chapters setMultilingual:YES languages:[self languages]];
            [Chapters setMultilingual:YES languages:nil];
    }
    NSDictionary *menus = [entry objectForKey:@"menus"];
    if (menus)
        Menus = [MHUtil spritesFromPlist:menus];
    return self;
}

- (void *)QTMovie
{
    if (!mainMovie || (isOffline && [[NSFileManager defaultManager] fileExistsAtPath:[data objectForKey:@"File Path"]])) {
		NSError *err = nil;
        mainMovie = [[QTMovie movieWithFile:stringPath error:&err] retain];
		if (err) NSLog(@"error opening movie at %@: %@", stringPath, err);
		[mainMovie setDelegate:self];
        if (mainMovie) {
            isOffline = NO;
            [self gatherData];
            if ([(MHChapters *)[self chapters] count])
                [[self chapters] addToMovie:self];
        } else {
            isOffline = YES;
            mainMovie = [[QTMovie movieWithURL:DUMMYURL error:nil] retain];
        }
    }
    if (chaptersNeedUpdating) {
        chaptersNeedUpdating = NO;
        [[self chapters] addToMovie:self];
    }
    return [mainMovie quickTimeMovie];
}

- (void)setMovie:(NSMovie *)movie
{
    [mainMovie release];
	if ([movie isKindOfClass:[QTMovie class]])
		mainMovie = [movie retain];
	else
		mainMovie = [[QTMovie movieWithQuickTimeMovie:[movie QTMovie] disposeWhenDone:NO error:nil] retain];
    [self gatherData];
}
/*
- (id)retain
{
    int before = [self retainCount];
    id me = [super retain];
    int after = [self retainCount];
    NSLog(@"%d retain %d", before, after);
    return me;
}

- (oneway void)release
{
    int before = [self retainCount];
    [super release];
    int after = 0;
    if (before > 1) after = [self retainCount];
    NSLog(@"%d release %d", before, after);
}
*/
- (void) dealloc
{
    [data release];
    [metadata release];
    [info release];
    [listNumbers release];
    [Chapters release];
    [stringPath release];
    if (tempFiles) {
        int i, n = [tempFiles count];
        NSFileManager *fm = [NSFileManager defaultManager];
        for (i=0;i<n;i++) {
            [fm removeFileAtPath:[tempFiles objectAtIndex:i] handler:nil];
        }
        [tempFiles release];
    }
    [mainMovie release];
	mainMovie = nil;
	return;  // ever since QTKit, QTMovie idleAllMovies crashes shortly after a MHMovie is released, even if the QTMovie is not released.
    [super dealloc];
}

- (NSURL *)URL
{
    if ([stringPath hasPrefix:@"/"])
        return [NSURL fileURLWithPath:stringPath];
    else
        return [NSURL URLWithString:stringPath];
}

- (BOOL)isFileWritable
{
	NSString *aFilePath = [data objectForKey:@"File Path"];
	return [[NSFileManager defaultManager] isWritableFileAtPath:aFilePath];
}

- (BOOL) addSeparateAudio
{
    if ([self trackCount] > 1) return NO;
    NSString *dirPath = [[data objectForKey:@"File Path"] stringByDeletingLastPathComponent];
    if (!dirPath) return NO;
    NSArray *files = [[NSFileManager defaultManager] directoryContentsAtPath:dirPath];
    int i, n = [files count];
    BOOL success = NO;
    NSArray *imageSufs = [NSArray arrayWithObjects:@".jpg", @".jpeg", @".gif", @".tif", @".tiff", @".pct", @".pict", @".png", nil];
	NSString *scrpt = nil;
    for (i=0;i<n;i++) {
        NSString *newPath = [dirPath stringByAppendingPathComponent:[files objectAtIndex:i]];
        if (![newPath isEqualToString:[data objectForKey:@"File Path"]]) {
            if ([newPath hasExtension:[NSArray arrayWithObjects:@"mov", @"mp3", @"m4a", nil]]) {
            //if ([[newPath lowercaseString] hasSuffix:@".mov"] || [[newPath lowercaseString] hasSuffix:@".mp3"] || [[newPath lowercaseString] hasSuffix:@".m4a"]) {
                MHMovie *newMovie = [[MHMovie alloc] initWithPath:newPath];
                if ([newMovie trackCount] == 1 && [[newMovie trackAtIndex:0] isSoundType]) {
                    SetMovieSelection([newMovie QTMovie], 0, GetMovieDuration([newMovie QTMovie]));
                    AddMovieSelection([self QTMovie], [newMovie QTMovie]);
                    [[self trackAtIndex:[self trackCount]-1] setName:[[newPath lastPathComponent] extensionRemoved]];
					[[self trackAtIndex:[self trackCount]-1] setLanguage:[[newPath lastPathComponent] extensionRemoved]];
                }
                [newMovie release];
                success = YES;
            } else if (![(MHChapters *)Chapters count] && [[newPath lowercaseString] hasSuffix:@".txt"]) {
                [Chapters parseTextFile:[NSURL fileURLWithPath:newPath]];
                success = YES;
			} else if ([[newPath lowercaseString] hasSuffix:@".mhscript"]) {
				scrpt = [NSString stringWithContentsOfFile:newPath];
            } else {
                int i, n = [imageSufs count];
                for (i=0;i<n;i++)
                    if ([[newPath lowercaseString] hasSuffix:[imageSufs objectAtIndex:i]]) {
                        [self setImageFile:newPath];
                        break;
                    }
            }
        }
    }
	if (scrpt) [self mhscript:scrpt];
	
    if (success) [self gatherData];
    chaptersNeedUpdating = YES;
    return success;
}

- (void) mhscript:(NSString *)input
{
	NSArray *list = [input componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\r\n"]];
	int i, n = [list count];
	for (i=0;i<n;i++) {
		NSString *line = [list objectAtIndex:i];
		NSArray *words = [line componentsSeparatedByString:@"}{"];
		int k = [words count];
		if (!k) continue;
		NSString *subject = nil, *verb = nil, *object = nil;
		if ([[words objectAtIndex:0] isEqual:@"track"]) {
			if (k > 1) {
				int index = [[words objectAtIndex:1] intValue];
				if (index >= 0 && index < [self trackCount]) subject = [self trackAtIndex:index];
				if (k > 2) verb = [words objectAtIndex:2];
				if (k > 3) object = [words objectAtIndex:3];
				if ([verb isEqual:@"name"] && object) [subject setName:object];
				if ([verb isEqual:@"language"] && object) [subject setLanguage:object];
			}
		} else if ([[words objectAtIndex:0] isEqual:@"create alternate group"]) {
			int i, n = [self trackCount];
			NSMutableArray *selected = [NSMutableArray array];
			for (i=0;i<n;i++)
				if ([[self trackAtIndex:i] isSoundType])
					[selected addObject:[self trackAtIndex:i]];
			n = [selected count];
			if (n > 1) {
				MHTrack *lastTrack = nil, *thisTrack;
				for (i=0;i<n;i++) {
					thisTrack = [selected objectAtIndex:i];
					[lastTrack setAlternate:thisTrack];
					lastTrack = thisTrack;
				}
				[lastTrack setAlternate:[selected objectAtIndex:0]];
			}
		} else if ([[words objectAtIndex:0] isEqual:@"metadata"]) {
			if (k > 1) verb = [words objectAtIndex:1];
			if (k > 2) object = [words objectAtIndex:2];
			if (verb && object) [self setObject:object forKey:verb];
		}
	}
}

- (BOOL) containsString:(NSString *)searchString
{
    //NSLog(@"MHMovie containsString");
    if (nil == searchString) return NO;
    NSArray *cats = [metadata allValues];
    int i, n = [cats count];
    for (i=0;i<n;i++) {
        NSDictionary *cat = [cats objectAtIndex:i];
        if ([cat isKindOfClass:[NSDictionary class]]) {
            int j, m = [[cat allValues] count];
            for (j=0;j<m;j++)
                if ([[[cat allValues] objectAtIndex:j] rangeOfString:searchString options:NSCaseInsensitiveSearch].length)
                    return YES;
        }
    }
    if (nil != Chapters)
        if ([Chapters containsString:searchString])
            return YES;
    return NO;
}

- (BOOL) saveToPath:(NSString *)path flatten:(BOOL)f
{
    //NSLog(@"MHMovie saveToPath");
    NSString *newPath;
    BOOL isDir;
    int suffix = 1;
    
    newPath = path;
    while ([[NSFileManager defaultManager] fileExistsAtPath:newPath isDirectory:&isDir])
        newPath = [NSString stringWithFormat:@"%@ %d", path, suffix++];
    
    NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
    NSString *textDump = [defs objectForKey:@"textDump"];
    /*
    if (!f) {
        [defs setObject:newPath forKey:@"textDump"];
        //[defs removeObjectForKey:@"textDump"];
        [Chapters addToMovie:self];
    }
    */
    [self updateQTMovie];
    
    if (!WriteMovieToPath([self QTMovie], newPath, f))
        return NO;
    if (textDump)
        [defs setObject:textDump forKey:@"textDump"];
    
    // Custom Icon
    IconFamily *icon = nil;
    NSString *iconPath = [info objectForKey:@"Cover Image"];
    if (nil == iconPath)
        return YES; 
    /*
    if ([iconPath hasPrefix:@"icns:"])
        icon = [IconFamily iconFamilyWithContentsOfFile:[iconPath substringFromIndex:5]];
    
    else if ([iconPath hasPrefix:@"icon:"])
        icon = [IconFamily iconFamilyWithIconOfFile:[iconPath substringFromIndex:5]];
        
    else if ([iconPath hasPrefix:@"frame:"]) {
        TimeValue tv = [[iconPath substringFromIndex:6] intValue];
        PicHandle picHandle = GetMoviePict([self QTMovie], tv);
        NSImage *image = nil;
        image = [MHUtil picHandleToImage:picHandle];
        if (image != nil)
            icon = [IconFamily iconFamilyWithThumbnailsOfImage:image];
            
    } else      */
        icon = [self iconFromFile:iconPath];
        
    if (icon != nil)
        [icon setAsCustomIconForFile:newPath];
    
    return YES;
}

- (BOOL) saveInPlace
{
	[self updateQTMovie];
	
	if ([[self qtKitMovie] canUpdateMovieFile]) {
		[Chapters setIgnoresTempFile:YES];
		if (![Chapters addToQTMovie:[[self qtKitMovie] quickTimeMovie]])
			NSLog(@"failed to add chapters");
		[Chapters setIgnoresTempFile:NO];
		
		return [[self qtKitMovie] updateMovieFile];
	}
    //return NO;
    FSRef fsRef;
    FSSpec fsSpec;
    OSErr err = noErr;
    short resRefNum;
    
    //[self updateQTMovie];
    err = FSPathMakeRef([stringPath fileSystemRepresentation], &fsRef, NULL /* isDirectory */);
    if (err != noErr)
        NSLog(@"failed to make FSRef");
    err = FSGetCatalogInfo(&fsRef, kFSCatInfoNone, NULL /* catalogInfo */, NULL /* outName */, &fsSpec, NULL /* parentRef */);
    if (err != noErr)
        NSLog(@"failed to make FSSpec");
    FSSpecPtr movieFile = &fsSpec;
    err = OpenMovieFile(movieFile, &resRefNum, fsWrPerm);
    if (err != noErr)
        NSLog(@"failed to open movie file");
    Movie fMovie = nil;
    short resID = movieInDataForkResID;
    Str255 resName;
    err = NewMovieFromFile(&fMovie, resRefNum, &resID, resName, newMovieActive, NULL);
    if (err != noErr)
        NSLog(@"failed to create movie");
    
    // metadata
    //[self updateQTMovie];
    err = CopyMovieUserData([self QTMovie], fMovie, kQTCopyUserDataReplace);
    if (err != noErr)
        NSLog(@"failed to copy user data");
    
    
    // chapters - remove dependency on textDump file.
    [Chapters setIgnoresTempFile:YES];
    if (![Chapters addToQTMovie:fMovie])
        NSLog(@"failed to add chapters");
    [Chapters setIgnoresTempFile:NO];
	
    // tracks, 4CCs, etc -- doesn't seem to work
    //int trackCount = GetMovieTrackCount(fMovie);
    //int i;
    
    //for (i=0;i<trackCount;i++)
    //    DisposeMovieTrack(GetMovieIndTrack(fMovie, 1));
    
    //SetMovieSelection([self QTMovie], 0, GetMovieDuration([self QTMovie]));
    //AddMovieSelection(fMovie, [self QTMovie]);
    
    err = UpdateMovieResource(fMovie, resRefNum, movieInDataForkResID, NULL);
    if (err != noErr)
        NSLog(@"failed to update movie resource");
    DisposeMovie(fMovie);
    CloseMovieFile(resRefNum);
    return YES;
}

- (void)cancelSave
{
    //NSLog(@"cancel save");
    [NSThread exit];
}

- (MHTrack *)trackAtIndex:(int)index
{
    Track t = GetMovieIndTrack([self QTMovie], index + 1);
    if (!t) return nil;
    return [[[MHTrack alloc] initWithTrack:t] autorelease];
}

- (int)trackCount
{
    return GetMovieTrackCount([self QTMovie]);
}

- (NSArray *)tracksOfType:(NSString *)type
{
    if (!type) return nil;
    NSMutableArray *array = [NSMutableArray array];
    int i, n = [self trackCount];
    for (i=0;i<n;i++) {
        MHTrack *track = [self trackAtIndex:i];
        if ([[track type] isEqualToString:type])
            [array addObject:track];
    }
    return array;
}

- (NSArray *)languages
{
    NSMutableArray *array = [NSMutableArray array];
    int i, n = [self trackCount];
    for (i=0;i<n;i++) {
        MHTrack *track = [self trackAtIndex:i];
        NSString *l = [track language];
        if (![array containsObject:l])
            [array addObject:l];
    }
    return array;
}

- (NSString *)currentLanguage
{
    int i, n = [self trackCount];
    for (i=0;i<n;i++) {
        MHTrack *track = [self trackAtIndex:i];
        if ([track alternate] && [track enabled])
            return [track language];
    }
    for (i=0;i<n;i++) {
        MHTrack *track = [self trackAtIndex:i];
        if ([track enabled])
            return [track language];
    }
    return @"Unspecified";
}

- (NSRect)displayFrame
{
    NSRect rect = NSMakeRect(0,0,0,0);
    int i, n = [self trackCount];
    BOOL first = YES;
    for (i=0;i<n;i++) {
        MHTrack *track = [self trackAtIndex:i];
        if (![track isSoundType]) {
            NSRect r = [track displayFrame];
            if (first || r.origin.x < rect.origin.x)
                rect.origin.x = r.origin.x;
            if (first || r.origin.y < rect.origin.y)
                rect.origin.y = r.origin.y;
            if (first || r.origin.x + r.size.width > rect.origin.x + rect.size.width)
                rect.size.width = r.origin.x + r.size.width - rect.origin.x;
            if (first || r.origin.y + r.size.height > rect.origin.y + rect.size.height)
                rect.size.height = r.origin.y + r.size.height - rect.origin.y;
            first = NO;
        }
    }
    return rect;
}

- (NSString *)summary
{
    NSLog(@"MHMovie summary");
    NSArray *keys = [metadata allKeys];
    int i, n = [keys count];
    NSString *key, *value;
    NSMutableString *output = [NSMutableString stringWithCapacity:1000];
    for (i=0;i<n;i++) {
        key = [keys objectAtIndex:i];
        value = [[metadata objectForKey:key] objectForKey:@"English"];
        [output appendString:[NSString stringWithFormat:@"%@: %@\n\n", key, value]];
    }
    return output;
}

- (NSString *)data
{
    //NSLog(@"MHMovie data");
    NSMutableString *output = [NSMutableString stringWithCapacity:1000];
    [output appendString:[NSString stringWithFormat:@"%@\n%@\n%.2f MB\n%@\n\n", [info objectForKey:@"File Name"], [data objectForKey:@"File Path"], [[data objectForKey:@"File Size"] doubleValue], [data objectForKey:@"Movie Length"]]];
    NSString *vCodec = [data objectForKey:@"Video Format"],
            *vFCC = [data objectForKey:@"Video FourCC"],
            *vVendor = [data objectForKey:@"Video Vendor"],
            *fps = [[data objectForKey:@"FPS"] stringValue],
            *vBitrate = [[data objectForKey:@"Video Bitrate"] stringValue],
            *w = [[data objectForKey:@"Video Width"] stringValue],
            *h = [[data objectForKey:@"Video Height"] stringValue],
            *aCodec = [data objectForKey:@"Audio Format"],
            *aBitrate = [[data objectForKey:@"Audio Bitrate"] stringValue],
            *aSampleRate = [[data objectForKey:@"Audio Sample Rate"] stringValue];
    [output appendString:[NSString stringWithFormat:@"Video:\n%@, %@, %@, %@ FPS, %@ KB/s, %@ x %@\n\nAudio:\n%@, %@ Kb/s, %@ Hz", vCodec, vFCC, vVendor, fps, vBitrate, w, h, aCodec, aBitrate, aSampleRate]];
    NSNumber *channels = [data objectForKey:@"Audio Channels"];
    if ([channels intValue] == 1)
        [output appendString:@", mono"];
    else if ([channels intValue] == 2)
        [output appendString:@", stereo"];
    else
        [output appendString:[NSString stringWithFormat:@", %d channels", [channels intValue]]];
    return output;
}

- (NSString *)stringValue
{
    //NSLog(@"MHMovie stringValue");
    NSString *title = [self objectForKey:@"Full Name" language:@"English"];
	if (!title) title = [self objectForKey:@"Full Name"];
    if (title) return title;
    if (nil == title)
        title = [info objectForKey:@"File Name"];
    if (nil == title)
        title = [[data objectForKey:@"File Path"] lastPathComponent];
    if (nil == title)
        title = [stringPath lastPathComponent];
    return [title extensionRemoved];
}

- (NSString *)description
{
    return [self stringValue];
}

- (TimeValue)duration
{
    return GetMovieDuration([self QTMovie]);
}

- (double)durationSeconds
{
    return (double)GetMovieDuration([self QTMovie]) / (double)GetMovieTimeScale([self QTMovie]);
}

- (NSString *)languageOfPictureResource:(pictureResource)r
{
    NSString *description = [NSString stringWithCString:r.description];
    NSRange range = [description rangeOfString:@":"];
    if (range.length) return [description substringToIndex:range.location];
    if (-1 != [tLanguages stringToNumber:description]) return description;
    return @"English";
}

- (NSImage *)image
{
    return [self imageForLanguage:@"English"];
}

/*  
    V2.3-2.4 encoding
    Text encoding      $xx
    MIME type          <text string> $00
    Picture type       $xx
    Description        <text string according to encoding> $00 (00)
    Picture data       <binary data> */

- (NSImage *)imageForLanguage:(NSString *)language
{
    //if (previewPic && [[previewPic name] isEqualToString:language]) return previewPic;
    Handle h = NewHandleClear(0);
    NSImage *image = nil;
    long i, count = CountUserDataType(GetMovieUserData([self QTMovie]), 'APIC');
    for (i=1;i<=count;i++) {
        OSErr err = GetUserData(GetMovieUserData([self QTMovie]), h, 'APIC', i);
        if (!err) {
            NSString *lang = nil;
            pictureResource r = parseTag(h);
            NSString *description = [NSString stringWithCString:r.description];
            NSRange range = [description rangeOfString:@":"];
            if (range.length) {
                lang = [description substringToIndex:range.location];
                description = [description substringFromIndex:range.location + 1];
            } else if (-1 != [tLanguages stringToNumber:description]) lang = description;
            else lang = @"English";
            
            if (!image || [language isEqualToString:lang]) {
                image = [[[NSImage alloc] initWithData:[NSData dataWithBytes:*(r.pictureData) length:GetHandleSize(r.pictureData)]] autorelease];
                [image setName:description];
            }
        }
    }
    DisposeHandle(h);
    //if (image) previewPic = [image retain];
    //else if (![stringPath hasSuffix:@".txt"]) { // generate preview
        
    //}
    
    return image;
}

- (void)removeImageForLanguage:(NSString *)language
{
    Handle h = NewHandleClear(0);
    UserData ud = GetMovieUserData([self QTMovie]);
    long i, count = CountUserDataType(ud, 'APIC');
    for (i=count; i>0; i--) {
        OSErr err = GetUserData(ud, h, 'APIC', i);
        if (!err) {
            if (!language || [language isEqualToString:[self languageOfPictureResource:parseTag(h)]]) {
                OSErr err = RemoveUserData(ud, 'APIC', i);
                if (err) NSLog(@"error removing APIC data: %d", err);
            }
        }
    }
    DisposeHandle(h);
    if (!language) [metadata removeObjectForKey:@"Cover Image"];
    else [[metadata objectForKey:@"Cover Image"] removeObjectForKey:language];
}

- (void)setImageFile:(NSString *)path
{
    [self setImageFile:path language:@"English"];
}

- (void)setImageFile:(NSString *)path language:(NSString *)language
{
    OSErr err = noErr;
    if (!path || ![path length]) {
        [self removeImageForLanguage:language];
        goto BAIL;
    }
    
    NSString *description = nil;
    if (!language) language = @"English";
    description = [NSString stringWithFormat:@"%@:%@", language, [path lastPathComponent]];
    
    pictureResource r;
    
    r.textEncoding = 0;
    r.mimeType = "image/jpeg";
    r.picType = CoverFront;
    r.description = (char *)[description cString];
    r.pictureData = nil;
    
    
    NSMutableArray *mimeArray = [NSMutableArray array];
    [mimeArray addObject:[NSArray arrayWithObjects:@"image/jpeg", @"jpg", @"jpeg", nil]];
    [mimeArray addObject:[NSArray arrayWithObjects:@"image/bmp", @"bmp", nil]];
    [mimeArray addObject:[NSArray arrayWithObjects:@"image/gif", @"gif", nil]];
    [mimeArray addObject:[NSArray arrayWithObjects:@"image/tiff", @"tif", @"tiff", nil]];
    [mimeArray addObject:[NSArray arrayWithObjects:@"image/png", @"png", nil]];
    int i, n = [mimeArray count];
    for (i=0;i<n;i++) {
        NSArray *mimes = [mimeArray objectAtIndex:i];
        if ([mimes containsObject:[[path extension] lowercaseString]]) {
            r.mimeType = (char *)[[mimes objectAtIndex:0] cString];
            break;
        }
    }
    NSData *imageData = [NSData dataWithContentsOfFile:path];
    if (!imageData) return;
    Handle h = NewHandleClear([imageData length]);
    BlockMoveData([imageData bytes], *h, [imageData length]);
    r.pictureData = h;
    h = createTag(r);
    
    // add it to the movie object
    err = AddUserData(GetMovieUserData([self QTMovie]), h, 'APIC');
    if (err)
        NSLog(@"error adding image to user data: %ld", err);
    else {
        NSMutableDictionary *cat = [metadata objectForKey:@"Cover Image"];
        if (!cat) {
            cat = [NSMutableDictionary dictionary];
            [metadata setObject:cat forKey:@"Cover Image"];
        }
        [cat setObject:[path lastPathComponent] forKey:language];
    }
    
    //if (previewPic) [previewPic release];
    //previewPic = [[NSImage alloc] initWithContentsOfFile:path];
BAIL:
    if (_sortedKeys) [_sortedKeys release];
    _sortedKeys = nil;
    [[NSNotificationCenter defaultCenter] postNotificationName:@"movieLanguageChangedNotification" object:self];
}

- (id)objectForKey:(id)anObject language:(NSString *)lang
{
	if (statusString) return statusString;
    return [[metadata objectForKey:anObject] objectForKey:lang];
}

- (id)objectForKey:(id)anObject
{
    //NSLog(@"MHMovie objectForKey");
	if (statusString) return statusString;
    NSDictionary *cat = [metadata objectForKey:anObject];
    NSArray *sysLangs = [tLanguages systemLanguages];
	NSString *sysLang = nil, *value = nil;
    int i;
    for (i=0;i<[sysLangs count];i++) {
        sysLang = [sysLangs objectAtIndex:i];
        value = [cat objectForKey:sysLang];
        if (value) break;
    }
    //NSString *value = [cat objectForKey:@"English"];
    if (!value)
        value = [data objectForKey:anObject];
    if (!value)
        value = [info objectForKey:anObject];
    return value;
}

- (BOOL)setObject:(id)anObject forKey:(id)key language:(NSString *)language
{
    id oldValue = nil;
    if (!key) return NO;
    if (!language || ![language length]) return [self setObject:anObject forKey:key];
    if ([key isEqual:@"Cover Image"]) {
        [self setImageFile:anObject language:language];
        goto BAIL;
    }
    NSMutableDictionary *cat = [metadata objectForKey:key];
    if (!cat) {
        cat = [NSMutableDictionary dictionary];
        [metadata setObject:cat forKey:key];
    }
    oldValue = [cat objectForKey:language];
    if (!oldValue) oldValue = @"";
    if (!anObject) anObject = @"";
    if ([oldValue isKindOfClass:[NSString class]] && [anObject isKindOfClass:[NSString class]] && [oldValue isEqualToString:anObject])
        return NO;
    if ([oldValue isKindOfClass:[NSNumber class]] && [anObject isKindOfClass:[NSNumber class]] && [[oldValue stringValue] isEqualToString:[anObject stringValue]])
        return NO;
    if ([anObject isKindOfClass:[NSString class]] && [anObject isEqualToString:@""])
        [cat removeObjectForKey:language];
    else
        [cat setObject:anObject forKey:language];
BAIL:
    [self setEdited:YES];
    if (_sortedKeys) [_sortedKeys release];
    _sortedKeys = nil;
    [[NSNotificationCenter defaultCenter] postNotificationName:@"liveUpdateNotification" object:nil userInfo:[NSDictionary dictionaryWithObject:self forKey:key]];
    return YES;
}

- (BOOL)setObject:(NSString *)anObject forKey:(NSString *)key
{
    //NSLog(@"MHMovie setObject:forKey:");
    id oldValue = nil;
    if (!key) return NO;
    if ([[NSArray arrayWithObjects:@"File Name", nil] containsObject:key]) {
        if (![anObject length]) [info removeObjectForKey:key];
        else [info setObject:anObject forKey:key];
        return YES;
    } else if ([key isEqualToString:@"Cover Image"]) {
        [self setImageFile:anObject language:nil];
        goto BAIL;
    }
    NSMutableDictionary *cat = [metadata objectForKey:key];
    if (!cat) {
        cat = [NSMutableDictionary dictionary];
        [metadata setObject:cat forKey:key];
    }
    NSArray *sysLangs = [tLanguages systemLanguages];
    NSString *sysLang = nil;
    if ([sysLangs count])
        sysLang = [sysLangs objectAtIndex:0];
    if (!sysLang) sysLang = @"English";
    oldValue = [cat objectForKey:sysLang];
    if (oldValue == nil)
        oldValue = @"";
    if (anObject == nil)
        anObject = @"";
    if ([oldValue isKindOfClass:[NSString class]] && [anObject isKindOfClass:[NSString class]] && [oldValue isEqualToString:anObject])
        return NO;
    if ([oldValue isKindOfClass:[NSNumber class]] && [anObject isKindOfClass:[NSNumber class]] && [[oldValue stringValue] isEqualToString:[(NSNumber *)anObject stringValue]])
        return NO;
    if ([anObject isKindOfClass:[NSString class]] && [anObject isEqualToString:@""])
        [cat removeObjectForKey:sysLang];
    else
        [cat setObject:anObject forKey:sysLang];
BAIL:
    [self setEdited:YES];
    if (_sortedKeys) [_sortedKeys release];
    _sortedKeys = nil;
    [[NSNotificationCenter defaultCenter] postNotificationName:@"liveUpdateNotification" object:nil userInfo:[NSDictionary dictionaryWithObject:self forKey:key]];
    return YES;
}

- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary
{
    NSArray *keys = [otherDictionary allKeys];
    int i, n = [keys count];
    for (i=0;i<n;i++) {
        NSString *anObject = [otherDictionary objectForKey:[keys objectAtIndex:i]];
        if (anObject)
            [self setObject:anObject forKey:[keys objectAtIndex:i]];
    }
    if (_sortedKeys) [_sortedKeys release];
    _sortedKeys = nil;
}

- (BOOL)setDataCode:(NSString *)newC forKey:(NSString *)key
{
    if (!newC) return NO;
    if (![newC isKindOfClass:[NSString class]]) return NO;
    const char *buffer = [newC cString];
    //Str255 pString;
    //CopyCStringToPascal(buffer, pString);
    if ([newC length] < 4) newC = [newC stringByAppendingString:@"    "];
    NSString *oldC = [data objectForKey:key];
    if (!oldC) return NO;
    if ([oldC isEqualToString:newC])
        return NO;
    OSType type = [newC characterAtIndex:0] * 256 * 256 * 256;
    type += [newC characterAtIndex:1] * 256 * 256;
    type += [newC characterAtIndex:2] * 256;
    type += [newC characterAtIndex:3];
    Track track = GetBiggestTrack([self QTMovie], VideoMediaType);
    if (!track)
        track = GetBiggestTrack([self QTMovie], MPEGMediaType);
    if (!track)
        return NO;
    Media media = GetTrackMedia(track);
    SampleDescriptionHandle desc = (SampleDescriptionHandle)NewHandle(sizeof(SampleDescription));
    GetMediaSampleDescription(media, 1, desc);
    ImageDescription *desc2 = *(ImageDescriptionHandle)desc;
    MoveHHi((Handle)desc);
    if ([key isEqualToString:@"Video FourCC"])
        desc2->cType = type;
    else if ([key isEqualToString:@"Video Vendor"])
        desc2->vendor = type;
    else if ([key isEqualToString:@"Video Format"])
        CopyCStringToPascal(buffer, desc2->name);
    else
        return NO;
    SetMediaSampleDescription(media, 1, desc);
    [data setObject:[NSString stringWithString:newC] forKey:key];
    [self setEdited:YES];
    saveInPlaceUnSafe = YES;
    return YES;
}

- (NSArray *)allKeys
{
    //NSLog(@"MHMovie allKeys");
    NSMutableArray *keys = [NSMutableArray array];
    [keys addObjectsFromArray:[metadata allKeys]];
    [keys addObjectsFromArray:[data allKeys]];
    [keys addObjectsFromArray:[info allKeys]];
    return keys;
}

- (NSArray *)allMetadataKeys
{
    return [metadata allKeys];
}

- (void)setListNumber:(int)number
{
    if (!listNumbers) listNumbers = [[NSMutableArray alloc] init];
    [listNumbers addObject:[NSNumber numberWithInt:number]];
}

- (NSDictionary *)defsEntry
{
    //NSLog(@"MHMovie defsEntry");
    NSMutableDictionary *entry = [NSMutableDictionary dictionary];
    [entry addEntriesFromDictionary:metadata];
    [entry addEntriesFromDictionary:data];
    [entry addEntriesFromDictionary:info];
	if (myRefMovie) [entry setObject:myRefMovie forKey:@"myRefMovie"];
    if (tempFiles) [entry setObject:tempFiles forKey:@"tempFiles"];
    if (nil != Chapters) {
        [entry setObject:[(MHChapters *)Chapters defsEntry] forKey:@"chapters"];
        [entry setObject:[NSNumber numberWithBool:[Chapters multilingual]] forKey:@"multiChaps"];
    }
    if (Menus)
        [entry setObject:[MHUtil plist:Menus] forKey:@"menus"];
    if (listNumbers)
        [entry setObject:[NSArray arrayWithArray:listNumbers] forKey:@"listNumbers"];
    [listNumbers release];
    listNumbers = nil;
    
    FSRef fsRef;
    NSString *path = [data objectForKey:@"File Path"];
    if (path) {
        const char *fsrep = [path fileSystemRepresentation];
        OSErr err = FSPathMakeRef(fsrep, &fsRef, NULL /* isDirectory */);
        if (!err) [entry setObject:[NSData dataWithBytes:&fsRef length:sizeof(fsRef)] forKey:@"FSRef"];
    }
    
    if (isChanged) [entry setObject:[NSNumber numberWithBool:YES] forKey:@"isChanged"];
    
    return entry;
}

- (void)setChapters:(id)chapters
{
    //[chapters setTimeScaleFromMovie:self];
    Chapters = chapters;
}

- (id)chapters
{
    return Chapters;
}

- (void)setChaptersNeedUpdating
{
    chaptersNeedUpdating = YES;
    [[NSNotificationCenter defaultCenter] postNotificationName:@"liveUpdateNotification" object:nil userInfo:[NSDictionary dictionaryWithObject:self forKey:@"Chapters"]];
}

- (void)setMenu:(id)menu
{	Menus = menu;	}

- (id)menu
{	return Menus;	}

- (void)saveCurrentHeight
{
    //NSLog(@"MHMovie saveCurrentHeight");
    Rect boxRect;
    GetMovieBox([self QTMovie], &boxRect);
    [data setObject:[NSNumber numberWithInt:boxRect.right - boxRect.left] forKey:@"Saved Width"];
    [data setObject:[NSNumber numberWithInt:boxRect.bottom - boxRect.top] forKey:@"Saved Height"];
}

- (void)addTempFilePath:(NSString *)aPath
{
    if (!aPath) return;
    if (!tempFiles) tempFiles = [[NSMutableArray alloc] init];
    [tempFiles addObject:aPath];
}

- (void)setStatus:(NSString *)status
{
	statusString = status;
}

- (BOOL)saveInPlaceUnSafe
{
    return saveInPlaceUnSafe;
}

- (void)setSaveInPlaceUnSafe
{
    saveInPlaceUnSafe = YES;
}

- (BOOL)isEdited
{
    return isChanged;
}
- (void)setEdited:(BOOL)e
{
    isChanged = e;
	/*
	if (e) {
		if (![mainMovie updateMovieFile])
			NSLog(@"updateMovieFile = NO.  %@", myRefMovie);
	}
	*/
}

- (BOOL)isOffline
{
    return isOffline;
}

- (double)currentTimeIndexSeconds
{
    return (double)[self currentTimeIndex] / (double)GetMovieTimeScale([self QTMovie]);
}

- (TimeValue)currentTimeIndex
{
    TimeValue tv;
    TimeRecord tr;
    tv = GetMovieTime([self QTMovie], &tr);
    return tv;
}

- (TimeValue)timeIndex
{
    return [[data objectForKey:@"Seconds Elapsed"] doubleValue] * (double)GetMovieTimeScale([self QTMovie]);
}

- (void)setCurrentTimeIndexSeconds:(double)seconds
{
    [self setCurrentTimeIndex:seconds * (double)GetMovieTimeScale([self QTMovie])];
}

- (void)setCurrentTimeIndex:(TimeValue)samples
{
    TimeRecord tr;
    GetMovieTime([self QTMovie], &tr);
    tr.value.lo = samples;
    SetMovieTime([self QTMovie], &tr);
}

- (void)setTimeIndex:(TimeValue)tv
{
    double secs = (double)tv / (double)GetMovieTimeScale([self QTMovie]);
    //NSLog(@"set time: %g", secs);
    [data setObject:[NSNumber numberWithDouble:secs] forKey:@"Seconds Elapsed"];
}

- (void)setSize:(int)size
{
    Rect boxRect;
    boxRect.top = boxRect.left = 0;
    boxRect.bottom = [[data objectForKey:@"Correct Height"] intValue];
    boxRect.right = [[data objectForKey:@"Correct Width"] intValue];
    switch (size) {
        case 1: boxRect.bottom *= 2;
                boxRect.right *= 2;
                break;
        case 2: boxRect.bottom = [[data objectForKey:@"Saved Height"] intValue];
                boxRect.right = [[data objectForKey:@"Saved Width"] intValue];
                break;
        default: break;
    }
    if (DEBUG)
	NSLog(@"setSize: %dx%d", boxRect.right, boxRect.bottom);
    if (boxRect.bottom && boxRect.right)
	SetMovieBox([self QTMovie], &boxRect);
}

- (void)updateQTMovie
{
    int i, j, m, n;
    NSString *value, *defaultValue, *theKey, *aLang;
    NSArray *keys = [[metadata allKeys] sortedArrayUsingSelector:@selector(compare:)];
    
	while (CountUserDataType(GetMovieUserData([self QTMovie]), '\251MTH'))
		RemoveUserData(GetMovieUserData([self QTMovie]), '\251MTH', 1);
	
    n = [keys count];
    for (i=0; i<n; i++) {
        theKey = [keys objectAtIndex:i];
        if ([theKey isEqualToString:@"Cover Image"]) continue;
        NSDictionary *cat = [metadata objectForKey:theKey];
        m = [[cat allKeys] count];
        defaultValue = nil;
        [self removeQTDataForKey:theKey];
        for (j=0;j<m;j++) {
            aLang = [[cat allKeys] objectAtIndex:j];
            value = [cat objectForKey:aLang];
            [self setQTData:value forKey:theKey forLanguage:aLang];
            if (!defaultValue) defaultValue = value;
        }
        NSArray *langs = [tLanguages selectedLanguages];
        if (![tLanguages toCopy])
            langs = [NSArray arrayWithObject:[NSNumber numberWithInt:0]]; // if we're not copying, then at least we're copying to english
        m = [langs count];
        if (defaultValue) {
            for (j=0;j<m;j++) {
                aLang = [tLanguages numberToString:[[langs objectAtIndex:j] intValue]];
                if (![[cat allKeys] containsObject:aLang])
                    [self setQTData:defaultValue forKey:theKey forLanguage:aLang];
            }
        }
    }
    // remove extras, just in case
    n = [[[self class] supportedMetadata] count];
    for (i=0;i<n;i++) {
        theKey = [[[self class] supportedMetadata] objectAtIndex:i];
        if (![metadata objectForKey:theKey])
            [self removeQTDataForKey:theKey];
    }
}

- (void)setMTHData:(NSString *)value forKey:(NSString *)key forLanguage:(NSString *)language
{
	short regionTag = [tLanguages stringToNumber:language];
	if (regionTag == -1) return;
	UserData ud = GetMovieUserData([self QTMovie]);
	OSErr err = noErr;
	OSType type = '\251MTH';
	Handle h = NewHandleClear(0);
	long i, count = CountUserDataType(ud, type);
	for (i=1;i<=count;i++)
		if (noErr != GetUserDataText(ud, h, type, i, regionTag)) break;
	DisposeHandle(h);
	if (i>count) {
		h = NewHandleClear(0);
		err = AddUserData(ud, h, type);
		DisposeHandle(h);
	}
	char *buffer = malloc(sizeof(char) * ([key length] + [value length] + 2));
	[key getCString:buffer];
	[value getCString:buffer+[key length]+1];
	h = NewHandleClear([key length]+[value length]+1);
	BlockMoveData(buffer, *h, [key length]+[value length]+1);
	err = AddUserDataText(ud, h, type, i, regionTag);
	if (err) NSLog(@"error adding MHT data: %d, %@, %@, %@", err, value, key, language);
	DisposeHandle(h);
	free(buffer);
}

- (void)setQTData:(NSString *)value forKey:(NSString *)key forLanguage:(NSString *)language
{
    if (!key || !language || !value) return;
    OSErr err = noErr;
    //NSLog(@"adding value: %@\nfor key: %@\nfor language: %@", value, key, language);
    UserData ud = GetMovieUserData([self QTMovie]);
    int length = [value length]; // + 1;
    Handle h = nil;
    if ([key isEqualToString:@"WLOC"]) {
        h = NewHandleClear(2*sizeof(short));
        short coords[2];
        sscanf([value cString], "%hd, %hd", &coords[1], &coords[0]);
        BlockMoveData(coords, *h, 2*sizeof(short));
        RemoveUserData(ud, 'WLOC', 1);
        if (noErr != AddUserData(ud, h, 'WLOC'))
            NSLog(@"error adding UserData: %@, WLOC", value);
    } else {
        h = NewHandleClear(length);
        OSType theType = [self stringToOSType:key];
        if (!theType) {
			[self setMTHData:value forKey:key forLanguage:language];
			return;
		}
        short regionTag = [tLanguages stringToNumber:language];
        if (regionTag == -1) return;
        char *buffer = malloc(sizeof(char) * (length + 1));
		
		//const char *buffer = [value cStringUsingEncoding:NSUnicodeStringEncoding];
		[value getSafeCString:buffer];
        BlockMoveData(buffer, *h, length);
        free(buffer);
		buffer = nil;
        long index = [key hasIndex];
        long count = CountUserDataType(ud, theType);
        if (index) {
            while ((count = CountUserDataType(ud, theType)) <= index) {
                Handle h2 = NewHandleClear(0);
                //**h2 = 'X';
                err = AddUserData(ud, h2, theType);
                if (err) NSLog(@"error adding UserData instance: %d", err);
                DisposeHandle(h2);
            }
        }
        index++;
        //count = CountUserDataType(ud, theType);
        if (err = AddUserDataText(ud, h, theType, index, regionTag))
            NSLog(@"error adding UserData:%d, %@, %@, %@", err, value, key, language);
    }
    DisposeHandle(h);
}

- (void) removeQTDataForKey:(NSString *)key
{
    if (!key) return;
    OSType theType = [self stringToOSType:key];
    int index = [key hasIndex];
    UserData ud = GetMovieUserData([self QTMovie]);
    if (!index)
        while (CountUserDataType(ud, theType))
            RemoveUserData(ud, theType, 1);
    else
        RemoveUserData(ud, theType, index+1);
}

- (BOOL) learn:(NSString *)newString key:(NSString *)key
{
    if (![newString length] || ![key length]) return NO;
    NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
    NSMutableDictionary *learned = [NSMutableDictionary dictionary];
    [learned addEntriesFromDictionary:[defs objectForKey:@"learned"]];
    NSMutableArray *cat = [NSMutableArray array];
    [cat addObjectsFromArray:[learned objectForKey:key]];
    NSArray *values = [newString componentsSeparatedByString:@", "];
    int i, n = [values count];
    for (i=0;i<n;i++) {
        NSString *value = [values objectAtIndex:i];
        if ([value length]) {
            if (NSNotFound == [cat indexOfObject:value])
                [cat addObject:value];
            else { // readd the value to the top because it was used again
                [cat removeObject:value];
                [cat addObject:value];
            }
        }
    }
    [learned setObject:cat forKey:key];
    [defs setObject:learned forKey:@"learned"];
    [defs synchronize];
    return YES;
}

- (void)makeSortedKeys
{
    if (_sortedKeys) [_sortedKeys release];
    _sortedKeys = [[NSMutableArray alloc] init];
    [_sortedKeys addObjectsFromArray:[metadata allKeys]];
    int i, n = [_sortedKeys count];
    for (i=n-1;i>=0;i--) {
        NSString *aKey = [_sortedKeys objectAtIndex:i];
        if ([aKey isEqualToString:@"WLOC"] || ![(NSDictionary *)[metadata objectForKey:aKey] count])
            [_sortedKeys removeObjectAtIndex:i];
    }
    [_sortedKeys sortUsingSelector:@selector(caseInsensitiveCompare:)];
    NSArray *supported = [[self class] supportedMetadata];
    n = [supported count];
    for (i=0;i<n;i++) {
        if (![_sortedKeys containsObject:[supported objectAtIndex:i]])
            [_sortedKeys addObject:[supported objectAtIndex:i]];
    }
    [_sortedKeys removeObject:@"Full Name"];
    [_sortedKeys insertObject:@"Full Name" atIndex:0];
}

// table view
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
    if (!_sortedKeys) [self makeSortedKeys];
    return [_sortedKeys count];
    //return [[MHMovie supportedMetadata] count];
}

- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
    if (!_sortedKeys) [self makeSortedKeys];
    NSString *value, *idString = [aTableColumn identifier];
    //NSArray *supported = [MHMovie supportedMetadata];
    if ([idString isEqualToString:@"Category"])
        return [_sortedKeys objectAtIndex:rowIndex];
    NSString *key = [_sortedKeys objectAtIndex:rowIndex];
    NSDictionary *dict = [metadata objectForKey:key];
    value = [dict objectForKey:idString];
    int i, n = [[tLanguages selectedLanguages] count];
    if ([tLanguages toCopy]) {
        for (i=0;i<n && !value;i++) {
            NSString *lang = [tLanguages numberToString:[[[tLanguages selectedLanguages] objectAtIndex:i] intValue]];
            value = [dict objectForKey:lang];
        }
    }
    //NSLog(@"%@: %@, %@ - %@", key, dict, value, idString);
    return value;
}

- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
    if (!_sortedKeys) [self makeSortedKeys];
    //if (!anObject || [anObject isEqualToString:@""]) return;
    NSString *lang = [aTableColumn identifier];
    NSString *key = [_sortedKeys objectAtIndex:rowIndex];
    [self setObject:anObject forKey:key language:lang];
	[self learn:anObject key:key];
	[[NSNotificationCenter defaultCenter] postNotificationName:@"movieEditNotification" object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:anObject, @"object", key, @"key", lang, @"lang", nil]];
}

- (BOOL) tableView: (NSTableView *) tableView shouldEditTableColumn: (NSTableColumn *) tableColumn row: (int) rowIndex
{
    return ![[tableColumn identifier] isEqualToString:@"Category"];
}

- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
    if ([[aTableColumn identifier] isEqualToString:@"Category"]) {
        NSArray *infoValues = [NSArray arrayWithObjects:@"Album", @"Author", @"Comment", @"Copyright", @"Description", @"Director", @"Full Name", @"Information", @"Performers", @"Writer", nil];
        NSFontTraitMask mask = NSUnboldFontMask;
        if ([infoValues containsObject:[aCell stringValue]])
            mask = NSBoldFontMask;
        [aCell setFont:[[NSFontManager sharedFontManager] convertFont:[aCell font] toHaveTrait:mask]];
    } else {
		
	}
}

BOOL recursion = NO;

- (void)textDidChange:(NSNotification *)aNotification
{
    //NSLog(@"textDidChange");
	if (recursion) return;
    NSTextView *box = [aNotification object];
    if (![[NSUserDefaults standardUserDefaults] boolForKey:@"autoComplete"]) return;
    NSString *item, *item2, *string = [NSString stringWithString:[box string]];
    NSRange selection = [box selectedRange];
    int initialLength = [string length];
    if ([string isEqualToString:@""] || [string hasSuffix:@", "] || selection.location != initialLength) return;
    NSRange aRange = [string rangeOfString:@", " options:NSBackwardsSearch];
    if (aRange.length != 0)
        string = [string substringFromIndex:aRange.location+2];
	id sview = box;
	while (YES) {
		sview = [sview superview];
		if ([sview isKindOfClass:[NSTableView class]]) break;
		if (!sview) return;
	}
	int row = [sview editedRow];
	if (row == -1) return;
	NSString *category = [_sortedKeys objectAtIndex:row];
	
    NSArray *list = [[[NSUserDefaults standardUserDefaults] objectForKey:@"learned"] objectForKey:category];
    if (!list) return;
    int i;
    recursion = YES;
    for (i=[list count]-1;i>=0;i--) {
        item = [list objectAtIndex:i];
        item2 = [item stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
        if ([item2 hasPrefix:string])
            item = item2;
        else if (![item hasPrefix:string])
            item = nil;
        if (item) {
            item = [item substringFromIndex:[string length]];
            [box insertText:item];
            selection.length = [item length];
            [box setSelectedRange:selection];
            break;
        }
    }
    recursion = NO;
}

- (void)readMTHMetadata
{
	UserData ud = GetMovieUserData([self QTMovie]);
	OSType type = '\251MTH';
	short itlRegionTag = 0;
	Handle h = NewHandleClear(0);
	long i, count = CountUserDataType(ud, type);
	for (i=1;i<=count;i++) {
		for (itlRegionTag=0; itlRegionTag <= 150; itlRegionTag++) {
			if (noErr == GetUserDataText(ud, h, type, i, itlRegionTag)) {
				NSString *lang = [tLanguages numberToString:itlRegionTag];
				int j, length = GetHandleSize(h);
				for (j=0;j<length;j++)
					if (((char *)*h)[j] == '\0') break;
				NSString *key = [NSString stringWithCString:(char *)*h length:j];
				if (j < length) {
					NSString *value = [NSString stringWithCString:(char *)*h+j+1 length:length-j-1];
					if ([key length] && [value length]) {
						NSMutableDictionary *cat = [metadata objectForKey:key];
						if (!cat) {
							cat = [NSMutableDictionary dictionary];
							[metadata setObject:cat forKey:key];
						}
						[cat setObject:value forKey:lang];
					}
				}
			}
		}
	}
}

- (void)readMetadata
{
    //NSLog(@"MHMovie readMetadata");
    UserData theUserData = GetMovieUserData([self QTMovie]);
    OSErr err = noErr;
    Handle dataHandle = NewHandle(0);
    short m, itlRegionTag = langEnglish;
    NSString *keyString = nil, *lang = nil;;
    NSMutableDictionary *cat = nil;
    long index = 0;
    
    OSType nextType = GetNextUserDataType(theUserData, 0);
    while (nextType != 0) {
        if ('WLOC' == nextType) {
            OSErr err = GetUserData(theUserData, dataHandle, nextType, 1);
            if (!err) {
                long size = GetHandleSize(dataHandle);
                if (size >= 2 * sizeof(short)) {
                    short *buffer = (short *)*dataHandle;
                    cat = [metadata objectForKey:@"WLOC"];
                    if (!cat) {
                        cat = [NSMutableDictionary dictionary];
                        [metadata setObject:cat forKey:@"WLOC"];
                    }
                    [cat setObject:[NSString stringWithFormat:@"%d, %d", buffer[1], buffer[0]] forKey:@"English"]; // WLOC coords seem to be flipped for some reason.
                }
            }
        } else if ('APIC' == nextType) {
            m = CountUserDataType(theUserData, nextType);
            for (index=1;index<=m;index++) {
                err = GetUserData(theUserData, dataHandle, nextType, index);
                if (!err) {
                    pictureResource r = parseTag(dataHandle);
                    NSString *description = [NSString stringWithCString:r.description];
                    NSRange range = [description rangeOfString:@":"];
                    if (range.length) {
                        lang = [description substringToIndex:range.location];
                        description = [description substringFromIndex:range.location+1];
                    } else if (-1 != [tLanguages stringToNumber:description]) lang = description;
                    else lang = @"English";
                    if (![description length]) description = @"a picture";
                    cat = [metadata objectForKey:@"Cover Image"];
                    if (!cat) {
                        cat = [NSMutableDictionary dictionary];
                        [metadata setObject:cat forKey:@"Cover Image"];
                    }
                    [cat setObject:description forKey:lang];
                }
            }
		} else if ('\251MTH' == nextType) {
			[self readMTHMetadata];
        } else if (((char *)(&nextType))[0] == '\251') {
            m = CountUserDataType(theUserData, nextType);
            for (index=1; index<=m; index++) {
                itlRegionTag = 0; // langEnglish
                while (itlRegionTag <= 150) {
                    if (noErr == GetUserDataText(theUserData, dataHandle, nextType, index, itlRegionTag)) {
                        //if (noErr == GetUserData(theUserData, dataHandle, nextType, index)) {
                        NSString *lang = [tLanguages numberToString:itlRegionTag];
                        keyString = [self osTypeToString:nextType];
                        if (keyString) {
                            if (index > 1)
                                keyString = [keyString stringByAppendingFormat:@" %ld", index - 1];
                            NSString *value = [NSString stringWithCString:(char *)*dataHandle length:GetHandleSize(dataHandle)];
                            if ([value length]) {
                                cat = [metadata objectForKey:keyString];
                                if (!cat) {
                                    cat = [NSMutableDictionary dictionary];
                                    [metadata setObject:cat forKey:keyString];
                                }
                                [cat setObject:value forKey:lang];
                            }
                        }
                    }
                    itlRegionTag++;
                }
            }
        }
        nextType = GetNextUserDataType(theUserData, nextType);
    }
    [_sortedKeys release];
    _sortedKeys = nil;
}

- (NSString *)osTypeToString:(OSType)type
{
    int i, n = [[MHMovie supportedMetadata] count];
    for (i=0;i<n;i++) {
        if (type == theKUserDataTypes[i])
            return [[MHMovie supportedMetadata] objectAtIndex:i];
    }
    char *buffer = (char *)&type;
    //if (buffer[0] != '\251')
    //    return nil;
    NSString *string = [NSString stringWithCString:buffer length:4];
    // check if it's just the caps version
    NSString *lowString = [string lowercaseString];
    OSType lowType = [self stringToOSType:lowString];
    for (i=0;i<n;i++)
        if (lowType == theKUserDataTypes[i])
            return [[MHMovie supportedMetadata] objectAtIndex:i];
    return string;
}

- (OSType)stringToOSType:(NSString *)string
{
    if (nil == string || [string isEqualToString:@""]) return 0;
    int i;
    char target = '\251';
    
    char first = [string characterAtIndex:0];
    if (first == target) {
        while ([string length] < 4)
            string = [string stringByAppendingString:@" "];
        long output = '\251' * 256 * 256 * 256;
        output += [string characterAtIndex:1] * 256 * 256;
        output += [string characterAtIndex:2] * 256;
        output += [string characterAtIndex:3];
        OSType type = output;
        return type;
    }
    i = [[MHMovie supportedMetadata] indexOfObject:string];
    if (NSNotFound == i) {
        NSString *last = [[string componentsSeparatedByString:@" "] lastObject];
        if ([last intValue]) string = [string substringToIndex:[string length] - [last length] - 1];
        i = [[MHMovie supportedMetadata] indexOfObject:string];
    }
    if (NSNotFound != i) return theKUserDataTypes[i];
    
    return 0;
}

- (NSRect) combineRect:(NSRect)aRect withRect:(NSRect)bRect
{
    // for top-left referenced rects
    NSRect r;
    // we want the lesser x
    if (aRect.origin.x < bRect.origin.x) r.origin.x = aRect.origin.x;
    else r.origin.x = bRect.origin.x;
    // we want the greater y;
    if (aRect.origin.y > bRect.origin.y) r.origin.y = aRect.origin.y;
    else r.origin.y = bRect.origin.y;
    // we want the greater x+width
    if (aRect.origin.x + aRect.size.width > bRect.origin.x + bRect.size.width)
        r.size.width = aRect.origin.x + aRect.size.width - r.origin.x;
    else r.size.width = bRect.origin.x + bRect.size.width - r.origin.x;
    // we want the lesser y-height;
    if (aRect.origin.y - aRect.size.height < bRect.origin.y - bRect.size.height)
        r.size.height = aRect.origin.y - aRect.size.height + r.origin.y;
    else r.size.height = bRect.origin.y - bRect.size.height + r.origin.y;
    
    return r;
}

- (void)gatherData
{
	// file system stuff
	NSDictionary *attributes = [[NSFileManager defaultManager] fileAttributesAtPath:stringPath traverseLink:YES];
	if (attributes) {
		NSDate *creationDate = [attributes objectForKey:NSFileCreationDate];
		[data setObject:creationDate forKey:@"File Creation Date"];
                NSDate *modificationDate = [attributes objectForKey:NSFileModificationDate];
                [data setObject:modificationDate forKey:@"File Modification Date"];
	}
	
    //NSLog(@"MHMovie gatherData");
    Movie mov = [self QTMovie];
    if ([data objectForKey:@"Seconds Elapsed"])
        [self setCurrentTimeIndexSeconds:[[data objectForKey:@"Seconds Elapsed"] doubleValue]];
    else
        [data setObject:[NSNumber numberWithDouble:0] forKey:@"Seconds Elapsed"];
    TimeValue duration = GetMovieDuration(mov);
    TimeScale scale = GetMovieTimeScale(mov);
    [data setObject:[NSNumber numberWithInt:duration/scale] forKey:@"Seconds"];
    NSString *movieLength = [MHUtil nSecondsToString:(double)duration/(double)scale];
    int i = -1;
    char ch;
    while (YES) {
        ch = [movieLength characterAtIndex:++i];
        if (ch != '0' && ch != ':') break;
    }
    OSErr theErr = noErr;
    wide dataSize;
    double mbSize;
    [data setObject:[movieLength substringFromIndex:i] forKey:@"Movie Length"];
    theErr = GetMovieDataSize64(mov, 0, duration, &dataSize);
    if (theErr != noErr) mbSize = -1;
    else mbSize = (double)Wide2LongLong(dataSize) / 1024.0 / 1024.0;
    [data setObject:[NSNumber numberWithDouble:mbSize] forKey:@"File Size"];
    Rect boxRect;
    GetMovieBox(mov, &boxRect);
    [data setObject:[NSNumber numberWithInt:boxRect.bottom-boxRect.top] forKey:@"Original Height"];
    [data setObject:[NSNumber numberWithInt:boxRect.bottom-boxRect.top] forKey:@"Saved Height"];
    [data setObject:[NSNumber numberWithInt:boxRect.right-boxRect.left] forKey:@"Original Width"];
    [data setObject:[NSNumber numberWithInt:boxRect.right-boxRect.left] forKey:@"Saved Width"];
    //NSLog(@"original: %dx%d", boxRect.right-boxRect.left, boxRect.bottom-boxRect.top);
    
    [data setObject:[self fpsForMovie:mov] forKey:@"FPS"];
    
    Track theTrack = nil;
    Media aMedia;
    char *buffer;
    //*width = *height = *vBitrate = *aBitrate = 0;
    
    // Video
    NSString *vCodec, *vFourCC, *vVendor;
    NSNumber *width, *height, *vBitrate;
    ImageDescriptionHandle vDesc = (ImageDescriptionHandle)NewHandle(sizeof(ImageDescription));
    
    //if (DEBUG)
//	NSLog(@"getting video...");
        theTrack = GetBiggestTrack(mov, VideoMediaType);    
        if (theTrack == nil) {
	   // NSLog(@"no video, getting mpeg...");
            theTrack = GetBiggestTrack(mov, MPEGMediaType);
        }
	if (theTrack == nil) {
	   // NSLog(@"no video track found");
            vCodec = vFourCC = vVendor = @"none";
            width = height = vBitrate = [NSNumber numberWithInt:0];
        } else {
	    MHTrack *track = [[[MHTrack alloc] initWithTrack:theTrack] autorelease];
            aMedia = GetTrackMedia(theTrack);
            GetMediaSampleDescription(aMedia, 1, (SampleDescriptionHandle)vDesc);
            MoveHHi((Handle)vDesc); //HLock((Handle)vDesc);
            height = [NSNumber numberWithInt:(*vDesc)->height];
            width = [NSNumber numberWithInt:(*vDesc)->width];
            buffer = (char *)&(*vDesc)->name + 1;
			int bufSize = *(*vDesc)->name;
#ifdef __LITTLE_ENDIAN__
buffer = rstring(buffer, bufSize);
#endif
            vCodec = [NSString stringWithCString:buffer];
            buffer = (char *)&(*vDesc)->cType;
#ifdef __LITTLE_ENDIAN__
buffer = rstring(buffer, 4);
#endif
            vFourCC = [NSString stringWithCString:buffer];
            buffer = (char *)&(*vDesc)->vendor;
#ifdef __LITTLE_ENDIAN__
buffer = rstring(buffer, 4);
#endif
            vVendor = [NSString stringWithCString:buffer];
            vBitrate = [NSNumber numberWithInt:[track mbSize] / ((double)GetTrackDuration(theTrack) / (double)scale)];
        }
        NSRect frame = NSMakeRect(0,0,0,0);
        int n = [self trackCount];
        for (i=0;i<n;i++) {
            NSRect aFrame = [[self trackAtIndex:i] displayFrame];
            frame = [self combineRect:frame withRect:aFrame];
        }
        [data setObject:[NSNumber numberWithInt:frame.size.height] forKey:@"Correct Height"];
        [data setObject:[NSNumber numberWithInt:frame.size.width] forKey:@"Correct Width"];
        //NSLog(@"correct: %gx%g", frame.size.width, frame.size.height);
    // Audio
    NSString *aCodec;
    NSNumber *aChannels, *sampleRate, *aBitrate;
    
    SoundDescriptionHandle sDesc = (SoundDescriptionHandle)NewHandle(sizeof(SoundDescription));
        theTrack = nil;
        theTrack = GetBiggestTrack(mov, SoundMediaType);
        if (nil == theTrack)
            theTrack = GetBiggestTrack(mov, MPEGMediaType);
        if (nil == theTrack) {
            aCodec = @"none";
            aChannels = sampleRate = aBitrate = [NSNumber numberWithInt:0];
        } else {
            aMedia = GetTrackMedia(theTrack);
            GetMediaSampleDescription(aMedia, 1, (SampleDescriptionHandle)sDesc);
            MoveHHi((Handle)sDesc); HLock((Handle)sDesc);
            aChannels = [NSNumber numberWithInt:(*sDesc)->numChannels];
            buffer = (char *)&(*sDesc)->dataFormat;
#ifdef __LITTLE_ENDIAN__
buffer = rstring(buffer, 4);
#endif
            aCodec = [NSString stringWithCString:buffer];
            sampleRate = [NSNumber numberWithInt:(*sDesc)->sampleRate >> 16];
            aBitrate = [NSNumber numberWithInt:GetTrackDataSize(theTrack, 0, GetTrackDuration(theTrack)) * 8 / (GetTrackDuration(theTrack) / scale) / 1024];
            buffer = (char *)&(*sDesc)->vendor;
        }
    
    NSArray *objects = [NSArray arrayWithObjects:vCodec, vFourCC, vVendor, width, height, vBitrate, aChannels, aCodec, sampleRate, aBitrate, nil];
    NSArray *keys = [NSArray arrayWithObjects:@"Video Format", @"Video FourCC", @"Video Vendor", @"Video Width", @"Video Height", @"Video Bitrate", @"Audio Channels", @"Audio Format", @"Audio Sample Rate", @"Audio Bitrate", nil];
    n = [objects count];
    for (i=0;i<n;i++) {
        [data setObject:[objects objectAtIndex:i] forKey:[keys objectAtIndex:i]];
    }
	int aCount = 0;
	while (YES) {
		theTrack = GetMovieIndTrackType(mov, aCount+1, SoundMediaType, movieTrackMediaType);
		if (nil == theTrack) break;
		else aCount++;
	}
	[data setObject:[NSNumber numberWithInt:aCount] forKey:@"Audio Count"];
    
    // TimeCode stuff
    UserData			myUserData;
    MediaHandler myHandler = GetMediaHandler(GetTrackMedia(GetMovieIndTrackType(mov, 1, TimeCodeMediaType, movieTrackMediaType)));
    if (myHandler) {
        theErr = TCGetCurrentTimeCode(myHandler, NULL, NULL, NULL, &myUserData);
        if (!theErr) {
            Handle h = NewHandleClear(0);
            GetUserDataText(myUserData, h, TCSourceRefNameType, 1, langEnglish);
            NSString *name = [NSString stringWithCString:*h length:GetHandleSize(h)];
            [data setObject:name forKey:@"Source Name"];
            if (h) DisposeHandle(h);
            DisposeUserData(myUserData);
        }
        TimeCodeDef			myTCDef;
        TimeCodeRecord                  myTCRec;
        theErr = TCGetCurrentTimeCode(myHandler, NULL, &myTCDef, &myTCRec, NULL);
        if (!theErr) {
            Str255		myString;
            theErr = TCTimeCodeToString(myHandler, &myTCDef, &myTCRec, myString);
            if (!theErr) {
                NSString *time = [NSString stringWithCString:myString+1 length:*myString];
                [data setObject:time forKey:@"Timecode"];
            }	
        }
    }
    [[NSNotificationCenter defaultCenter] postNotificationName:@"liveUpdateNotification" object:nil userInfo:[NSDictionary dictionaryWithObject:self forKey:@"ALL"]];
}

- (NSNumber *)fpsForMovie:(Movie)theMovie
{
    TimeValue 			start = GetMovieDuration(theMovie) / 2;
    TimeValue			end = start + (GetMovieTimeScale(theMovie) * 10);
    TimeValue			duration = GetMovieDuration(theMovie);
	
	if (end > duration) end = duration;
	
	int numFrames = [MHUtil framesBetweenStart:start end:end movie:[self QTMovie]];
	
	double fps = (double)numFrames / ((double)(end - start) / (double)GetMovieTimeScale(theMovie));
	
	//fps += 0.49; // for proper rounding
	
	return [NSNumber numberWithInt:fps];
}


- (IconFamily *)iconFromFile:(NSString *)path
{
    if (path == nil) return nil;
    IconFamily *icon = nil;
    NSImage *image = nil;
    if ([path hasSuffix:@".icns"])
        icon = [IconFamily iconFamilyWithContentsOfFile:path];
    else {
        if ([path hasPrefix:@"http:"] || [path hasPrefix:@"ftp:"])
            image = [[NSImage alloc] initWithContentsOfURL:[NSURL URLWithString:path]];
        else
            image = [[NSImage alloc] initWithContentsOfFile:path];
        if (image != nil) {
            [image retain];
            icon = [IconFamily iconFamilyWithThumbnailsOfImage:image];
        }
    }
    return icon;
}

- (BOOL)askForMovie
{
    NSOpenPanel *theOpenPanel = [NSOpenPanel openPanel];
    int result;
    
    [theOpenPanel setTitle:[NSString stringWithFormat:@"Please Locate %@",[stringPath lastPathComponent]]];
    [theOpenPanel setCanChooseFiles:YES];
    [theOpenPanel setCanChooseDirectories:NO];
    [theOpenPanel setResolvesAliases:YES];
    [theOpenPanel setAllowsMultipleSelection:NO];
    result = [theOpenPanel runModalForDirectory:nil file:nil types:nil];
    if (result == NSOKButton) {
        stringPath = [[theOpenPanel filenames] objectAtIndex:0];
        return YES;
    }
    return NO;
}

int IsChapterTrack(Track track) {
    Movie mov = GetTrackMovie(track);
    int i, n = GetMovieTrackCount(mov);
    for (i=1;i<=n;i++) {
        if (GetTrackReference(GetMovieIndTrack(mov, i), kTrackReferenceChapterList, 1) == track) return i;
    }
    return NO;
}


// from MyMovie.m
// http://developer.apple.com/samplecode/Sample_Code/Cocoa/bMoviePalette/MyMovie.m.htm
static BOOL WriteMovieToPath(Movie movie, NSString *path, BOOL flatten)
{
    // get QT version
    long version;
    OSErr err;
    FSRef    fsRef;
    FSSpec   fsSpec;
    short    resRefNum;
    err = Gestalt(gestaltQuickTime, &version);
    if ((noErr == err) && version >= 0x06408000)
    {
        // QT version is 6.4 or higher
        Handle dataRef = NULL;
        OSType dataRefType;
        DataHandler dataHandler;
        
        err = QTNewDataReferenceFromFullPathCFString((CFStringRef)path, kQTNativeDefaultPathStyle, 0, &dataRef, &dataRefType);
        if (flatten) {
            Movie mov = FlattenMovieDataToDataRef(movie, flattenAddMovieToDataFork | flattenForceMovieResourceBeforeMovieData | flattenCompressMovieResource, dataRef, dataRefType, FOUR_CHAR_CODE('TVOD'), smSystemScript, createMovieFileDeleteCurFile);
            if (!mov) NSLog(@"couldn't flatten");
        } else { // not flattening
            Track aTrack;
            // find any chapter tracks
            int i, j, n = GetMovieTrackCount(movie);
            NSMutableArray *chapTrackNumbers = [NSMutableArray array];
            NSMutableArray *targTrackNumbers = [NSMutableArray array];
            for (i=1;i<=n;i++) {
                aTrack = GetMovieIndTrack(movie, i);
                int target = IsChapterTrack(aTrack);
                if (target) {
                    [chapTrackNumbers addObject:[NSNumber numberWithInt:i]];
                    [targTrackNumbers addObject:[NSNumber numberWithInt:target]];
                }
            }
            // adjust the target numbers for moving chapters to the start
            NSMutableArray *newTargTrackNumbers = [NSMutableArray array];
            for (i=0;i<[targTrackNumbers count];i++) {
                //int subtractor = 0;
                int targNum = [[targTrackNumbers objectAtIndex:i] intValue];
                // subtract one for every chapter track below it
                for (j=0;j<[chapTrackNumbers count];j++)
                    if ([[chapTrackNumbers objectAtIndex:j] intValue] < targNum)
                        targNum--;
                // add the number of chapter tracks, since they will all be at the start
                targNum += [chapTrackNumbers count];
                [newTargTrackNumbers addObject:[NSNumber numberWithInt:targNum]];
            }
            targTrackNumbers = newTargTrackNumbers;
            
            // save the chapter tracks to a new movie
            if ([chapTrackNumbers count]) {
                MovieEditState srcState = NewMovieEditState(movie);
                
                // remove chapter references
                for (i=1;i<GetMovieTrackCount(movie);i++) {
                    aTrack = GetTrackReference(GetMovieIndTrack(movie, i), kTrackReferenceChapterList, 1);
                    if (aTrack)
                        err = DeleteTrackReference(GetMovieIndTrack(movie, i), kTrackReferenceChapterList, 1);
                }
                
                // remove non-chapter tracks
                for (i=GetMovieTrackCount(movie);i>0;i--) {
                    if (![chapTrackNumbers containsObject:[NSNumber numberWithInt:i]])
                        DisposeMovieTrack(GetMovieIndTrack(movie, i));
                }
                
                // save and revert
                WriteMovieToPath(movie, path, YES);
                err = UseMovieEditState(movie, srcState);
                err = DisposeMovieEditState(srcState);
                srcState = NewMovieEditState(movie);
                
                // remove chapter tracks
                for (i=[chapTrackNumbers count]-1;i>=0;i--) {
                    int index = [[chapTrackNumbers objectAtIndex:i] intValue];
                    DisposeMovieTrack(GetMovieIndTrack(movie, index));
                }
                
                // update new saved movie
                err = FSPathMakeRef([path fileSystemRepresentation], &fsRef, NULL);
                err = FSGetCatalogInfo(&fsRef, kFSCatInfoNone, NULL, NULL, &fsSpec, NULL);
                err = OpenMovieFile(&fsSpec, &resRefNum, fsWrPerm);
                Movie newMov = nil;
                short resID = movieInDataForkResID;
                Str255 resName;
                err = NewMovieFromFile(&newMov, resRefNum, &resID, resName, newMovieActive, NULL);
                
                SetMovieSelection(movie, 0, 0);
                SetMovieSelection(newMov, 0, 0);
                AddMovieSelection(newMov, movie);
                err = CopyMovieUserData(movie, newMov, kQTCopyUserDataReplace);
                
                // restore chapter references
                for (i=0;i<[chapTrackNumbers count];i++) {
                    int targT = [[targTrackNumbers objectAtIndex:i] intValue];
                    AddTrackReference(GetMovieIndTrack(newMov, targT), GetMovieIndTrack(newMov, i+1), kTrackReferenceChapterList, NULL);
                }
                
                err = UpdateMovieResource(newMov, resRefNum, movieInDataForkResID, NULL);
                DisposeMovie(newMov);
                CloseMovieFile(resRefNum);
                
                // revert again
                err = UseMovieEditState(movie, srcState);
                err = DisposeMovieEditState(srcState);
                srcState = nil;
                
                if (err) return NO;
                return YES;
            } else { // no chapters
                err = CreateMovieStorage(dataRef, dataRefType, FOUR_CHAR_CODE('TVOD'), smSystemScript, 0, // createMovieFileDeleteCurFile,
                    &dataHandler, NULL);
                //NSMovie *refMov = [[NSMovie alloc] initWithURL:[NSURL fileURLWithPath:path] byReference:NO];
                err = UpdateMovieInStorage(movie, dataHandler);
                //AddMovieToStorage(movie, dataHandler);
                err = CloseMovieStorage(dataHandler);
                //CloseComponent(dataHandler);
                /* rsrc fork style
                if (![[NSFileManager defaultManager] fileExistsAtPath:path]) [@"" writeToFile:path atomically:NO];
                err = FSPathMakeRef([path fileSystemRepresentation], &fsRef, NULL);
                err = FSGetCatalogInfo(&fsRef, kFSCatInfoNone, NULL, NULL, &fsSpec, NULL);
                short resId = 0, newResId = 0;
                Movie newmovie = NULL;
                err = CreateMovieFile(&fsSpec, 'TVOD', smSystemScript, nil, &resId, &newmovie);
                err = AddMovieResource(movie, resId, &newResId, nil);
                CloseMovieFile(resId);
                */
            }
        }
        DisposeHandle(dataRef);
        if (err) return NO;
        return YES;
    }
    // QT is less than 6.4
    if (!flatten) {
        Handle h = NewHandle(0);
        PutMovieIntoHandle(movie, h);
        HLock(h);
        NSData *d = [NSData dataWithBytes:*h length:GetHandleSize(h)];
        DisposeHandle(h);
        [d writeToFile:path atomically:YES];
        return YES;
    }
    // flattening, old QT
    NSString *newPath;
    NSFileManager *fm = [NSFileManager defaultManager];
    
    // old QT don't like long file names
    /*
    newPath = [NSString stringWithFormat:@"%@~",path];
    newName = [newPath lastPathComponent];
    if ([newName length] >= 25) {
        newName = [newName substringToIndex:25];
        newFolder = [newPath stringByDeletingLastPathComponent];
        newPath = [newFolder stringByAppendingPathComponent:newName];
        tempPath = newPath;
        int count = 1;
        while ([fm fileExistsAtPath:tempPath])
            tempPath = [NSString stringWithFormat:@"%@%d", newPath, count++];
        newPath = tempPath;
    }
    */
    if (![fm fileExistsAtPath:path])
        [[NSString stringWithString:@"this file is empty"] writeToFile:path atomically:NO];
    err = FSPathMakeRef([newPath fileSystemRepresentation], &fsRef, NULL);
    if (err) return NO;
    err = FSGetCatalogInfo(&fsRef,
                kFSCatInfoNone,
                NULL, // catalogInfo
                NULL, // outName
                &fsSpec, NULL // parentRef
                );

    if (err) return NO;
    short resId;
    FlattenMovie(movie,
            flattenAddMovieToDataFork | flattenForceMovieResourceBeforeMovieData | flattenCompressMovieResource,
            &fsSpec,'TVOD',
            smSystemScript,
            createMovieFileDeleteCurFile | createMovieFileDontCreateResFile,
                &resId,nil);
    CloseMovieFile(resId);
     /*
        old ref mov method
                Movie newmovie;
                short resId;
                OSType creatorType = FOUR_CHAR_CODE('TVOD');
                err = CreateMovieFile(&fsSpec, creatorType, smSystemScript, nil, &resId, &newmovie); 
                err = AddMovieResource (movie, resId, &newResId, nil);
                CloseMovieFile(resId);
    */
    rename([newPath fileSystemRepresentation],[path fileSystemRepresentation]);
    //[[NSFileManager defaultManager] movePath:newPath toPath:path handler:nil];
    return YES;
        /* Clean up here */
    //unlink([newPath fileSystemRepresentation]);
}

Track GetBiggestTrack(Movie qtMovie, OSType type) {
    Track aTrack, theTrack;
    aTrack = theTrack = nil;
    int i = 1;
    wide w;
    if (nil == qtMovie) return nil;
    w.hi = w.lo = 0;
    long long newsize, oldsize;
    newsize = oldsize = 0;
    OSErr err = noErr;
    aTrack = GetMovieIndTrackType(qtMovie, i++, type, movieTrackMediaType);
    while (aTrack != nil) {
        err = GetTrackDataSize64(aTrack, 0, GetTrackDuration(aTrack), &w);
	newsize = Wide2LongLong(w);
	if (DEBUG)
	    NSLog(@"size %d: %lld", i-1, newsize);
        if (!theTrack || newsize > oldsize) {
            oldsize = newsize;
            theTrack = aTrack;
        }
        aTrack = GetMovieIndTrackType(qtMovie, i++, type, movieTrackMediaType);
    }
    if (DEBUG)
	NSLog(@"GetBiggestTrack: %d tracks tested", i-2);
    return theTrack;
}

long long Wide2LongLong(wide w) {
    long long result = (((long long)w.hi)<<32)|w.lo;
    return result;
}

char *rstring(char *s, int length) {
	char *buffer = malloc(sizeof(char)*(length+1));
	buffer[length] = '\0';
	int i, j;
	for (i=0,j=length-1;i<length;i++,j--) {
		buffer[j] = s[i];
	}
	return buffer;
}

@end
