root/branches/perian-1.1/Subtitles/SubImport.mm

Revision 923, 31.1 kB (checked in by astrange, 4 months ago)

Merge trunk to the branch.
Disable ssse3 qpel stuff (https://roundup.mplayerhq.hu/roundup/ffmpeg/issue463)

Line 
1 //
2 //  SubImport.m
3 //  SSARender2
4 //
5 //  Created by Alexander Strange on 7/24/07.
6 //  Copyright 2007 __MyCompanyName__. All rights reserved.
7 //
8
9 #include <QuickTime/QuickTime.h>
10 #include "CommonUtils.h"
11 #include "Codecprintf.h"
12 #include "CodecIDs.h"
13 #import "SubImport.h"
14 #import "SubParsing.h"
15 #import "SubUtilities.h"
16
17 //#define SS_DEBUG
18
19 #pragma mark C
20
21 // if the subtitle filename is something like title.en.srt or movie.fre.srt
22 // this function detects it and returns the subtitle language
23 short GetFilenameLanguage(CFStringRef filename)
24 {
25         CFRange findResult;
26         CFStringRef baseName = NULL;
27         CFStringRef langStr = NULL;
28         short lang = langUnspecified;
29        
30         // find and strip the extension
31         findResult = CFStringFind(filename, CFSTR("."), kCFCompareBackwards);
32         findResult.length = findResult.location;
33         findResult.location = 0;
34         baseName = CFStringCreateWithSubstring(NULL, filename, findResult);
35        
36         // then find the previous period
37         findResult = CFStringFind(baseName, CFSTR("."), kCFCompareBackwards);
38         findResult.location++;
39         findResult.length = CFStringGetLength(baseName) - findResult.location;
40        
41         // check for 3 char language code
42         if (findResult.length == 3) {
43                 char langCStr[4] = "";
44                
45                 langStr = CFStringCreateWithSubstring(NULL, baseName, findResult);
46                 CFStringGetCString(langStr, langCStr, 4, kCFStringEncodingASCII);
47                 lang = ISO639_2ToQTLangCode(langCStr);
48                
49                 CFRelease(langStr);
50                
51                 // and for a 2 char language code
52         } else if (findResult.length == 2) {
53                 char langCStr[3] = "";
54                
55                 langStr = CFStringCreateWithSubstring(NULL, baseName, findResult);
56                 CFStringGetCString(langStr, langCStr, 3, kCFStringEncodingASCII);
57                 lang = ISO639_1ToQTLangCode(langCStr);
58                
59                 CFRelease(langStr);
60         }
61        
62         CFRelease(baseName);
63         return lang;
64 }
65
66 static bool ShouldEngageFrontRowHack(void)
67 {
68         bool ret;
69         Boolean isSet;
70        
71         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
72         NSString *applicationName = [[NSProcessInfo processInfo] processName];
73         long minorVersion;
74         Gestalt(gestaltSystemVersionMinor, &minorVersion);
75         CFPreferencesGetAppBooleanValue(CFSTR("PerianFrontRowSubtitleHack"),CFSTR("org.perian.Perian"),&isSet);
76        
77         ret = (minorVersion == 5) && [applicationName isEqualToString:@"Front Row"] && isSet;
78         [pool release];
79        
80         return ret;
81 }
82
83 Track CreatePlaintextSubTrack(Movie theMovie, ImageDescriptionHandle imgDesc,
84                               TimeScale timescale, Handle dataRef, OSType dataRefType, FourCharCode subType, Handle imageExtension, Rect movieBox)
85 {
86         Fixed trackWidth, trackHeight;
87         Track theTrack;
88         Media theMedia;
89        
90         // plain text subs have no size on their own
91         trackWidth = IntToFixed(movieBox.right - movieBox.left);
92         trackHeight = IntToFixed(movieBox.bottom - movieBox.top);
93        
94         (*imgDesc)->idSize = sizeof(ImageDescription);
95         (*imgDesc)->cType = subType;
96         (*imgDesc)->width = FixedToInt(trackWidth);
97         (*imgDesc)->height = FixedToInt(trackHeight);
98         (*imgDesc)->frameCount = 1;
99         (*imgDesc)->depth = 32;
100         (*imgDesc)->clutID = -1;
101        
102         if (!trackWidth || !trackHeight) {trackWidth = IntToFixed(640); trackHeight = IntToFixed(480);}
103        
104         if (imageExtension) AddImageDescriptionExtension(imgDesc, imageExtension, subType);
105        
106         theTrack = NewMovieTrack(theMovie, trackWidth, trackHeight, kNoVolume);
107         if (theTrack != NULL) {
108                 theMedia = NewTrackMedia(theTrack, VideoMediaType, timescale, dataRef, dataRefType);
109                
110                 if (theMedia != NULL) {
111                         // finally, say that we're transparent
112                         MediaHandler mh = GetMediaHandler(theMedia);
113                        
114                         if (ShouldEngageFrontRowHack()) {
115                                 RGBColor blendColor = {0x0000, 0x0000, 0x0000};
116                                 MediaSetGraphicsMode(mh, transparent, &blendColor);
117                         }
118                         else MediaSetGraphicsMode(mh, graphicsModePreBlackAlpha, NULL);
119                        
120                         // subtitle tracks should be above the video track, which should be layer 0
121                         SetTrackLayer(theTrack, -1);
122                 } else {
123                         DisposeMovieTrack(theTrack);
124                         theTrack = NULL;
125                 }
126         }
127        
128         return theTrack;
129 }
130
131 static NSString *MatroskaPacketizeLine(NSDictionary *sub, int n)
132 {
133         NSString *name = [sub objectForKey:@"Name"];
134         if (!name) name = [sub objectForKey:@"Actor"];
135        
136         return [NSString stringWithFormat:@"%d,%d,%@,%@,%@,%@,%@,%@,%@\n",
137                 n+1,
138                 [[sub objectForKey:@"Layer"] intValue],
139                 [sub objectForKey:@"Style"],
140                 [sub objectForKey:@"Name"],
141                 [sub objectForKey:@"MarginL"],
142                 [sub objectForKey:@"MarginR"],
143                 [sub objectForKey:@"MarginV"],
144                 [sub objectForKey:@"Effect"],
145                 [sub objectForKey:@"Text"]];
146 }
147
148 static unsigned ParseSSATime(NSString *time)
149 {
150         unsigned hour, minute, second, millisecond;
151        
152         sscanf([time UTF8String],"%u:%u:%u.%u",&hour,&minute,&second,&millisecond);
153        
154         return hour * 100 * 60 * 60 + minute * 100 * 60 + second * 100 + millisecond;
155 }
156
157 static NSString *LoadSSAFromPath(NSString *path, SubSerializer *ss)
158 {
159         NSString *nssSub = STLoadFileWithUnknownEncoding(path);
160        
161         if (!nssSub) return nil;
162        
163         size_t slen = [nssSub length], flen = sizeof(unichar) * (slen+1);
164         unichar *subdata = (unichar*)malloc(flen);
165         [nssSub getCharacters:subdata];
166        
167         if (subdata[slen-1] != '\n') subdata[slen++] = '\n'; // append newline if missing
168        
169         NSDictionary *headers;
170         NSArray *subs;
171        
172         SubParseSSAFile(subdata, slen, &headers, NULL, &subs);
173         free(subdata);
174        
175         int i, numlines = [subs count];
176        
177         for (i = 0; i < numlines; i++) {
178                 NSDictionary *sub = [subs objectAtIndex:i];
179                 SubLine *sl = [[SubLine alloc] initWithLine:MatroskaPacketizeLine(sub, i)
180                                                                                           start:ParseSSATime([sub objectForKey:@"Start"]) end:ParseSSATime([sub objectForKey:@"End"])];
181                
182                 [ss addLine:sl];
183                 [sl autorelease];
184         }
185                
186         return [nssSub substringToIndex:[nssSub rangeOfString:@"[Events]" options:NSLiteralSearch].location];
187 }
188
189 #pragma mark SAMI Parsing
190
191 static void LoadSRTFromPath(NSString *path, SubSerializer *ss)
192 {
193         NSMutableString *srt = STStandardizeStringNewlines(STLoadFileWithUnknownEncoding(path));
194         if (!srt) return;
195                
196         if ([srt characterAtIndex:0] == 0xFEFF) [srt deleteCharactersInRange:NSMakeRange(0,1)];
197         if ([srt characterAtIndex:[srt length]-1] != '\n') [srt appendFormat:@"%c",'\n'];
198        
199         NSScanner *sc = [NSScanner scannerWithString:srt];
200         NSString *res=nil;
201         [sc setCharactersToBeSkipped:nil];
202        
203         int h, m, s, ms;
204         unsigned startTime=0, endTime=0;
205        
206         enum {
207                 INITIAL,
208                 TIMESTAMP,
209                 LINES
210         } state = INITIAL;
211        
212         do {
213                 switch (state) {
214                         case INITIAL:
215                                 if ([sc scanInt:NULL] == TRUE && [sc scanUpToString:@"\n" intoString:&res] == FALSE) {
216                                         state = TIMESTAMP;
217                                         [sc scanString:@"\n" intoString:nil];
218                                 } else
219                                         [sc setScanLocation:[sc scanLocation]+1];
220                                 break;
221                         case TIMESTAMP:
222                                 [sc scanInt:&h];  [sc scanString:@":" intoString:nil];
223                                 [sc scanInt:&m];  [sc scanString:@":" intoString:nil];                         
224                                 [sc scanInt:&s];  [sc scanString:@"," intoString:nil];                         
225                                 [sc scanInt:&ms]; [sc scanString:@" --> " intoString:nil];
226                                 startTime = ms + s*1000 + m*1000*60 + h*1000*60*60;
227                                 [sc scanInt:&h];  [sc scanString:@":" intoString:nil];
228                                 [sc scanInt:&m];  [sc scanString:@":" intoString:nil];                         
229                                 [sc scanInt:&s];  [sc scanString:@"," intoString:nil];                         
230                                 [sc scanInt:&ms]; [sc scanString:@"\n" intoString:nil];
231                                 endTime = ms + s*1000 + m*1000*60 + h*1000*60*60;
232                                 state = LINES;
233                                 break;
234                         case LINES:
235                                 [sc scanUpToString:@"\n\n" intoString:&res];
236                                 [sc scanString:@"\n\n" intoString:nil];
237                                 SubLine *sl = [[SubLine alloc] initWithLine:res start:startTime end:endTime];
238                                 [ss addLine:[sl autorelease]];
239                                 state = INITIAL;
240                                 break;
241                 };
242         } while (![sc isAtEnd]);
243 }
244
245 static int parse_SYNC(NSString *str)
246 {
247         NSScanner *sc = [NSScanner scannerWithString:str];
248
249         int res;
250
251         if ([sc scanString:@"START=" intoString:nil])
252                 [sc scanInt:&res];
253
254         return res;
255 }
256
257 static NSArray *parse_STYLE(NSString *str)
258 {
259         NSScanner *sc = [NSScanner scannerWithString:str];
260
261         NSString *firstRes;
262         NSString *secondRes;
263         NSArray *subArray;
264         int secondLoc;
265
266         [sc scanUpToString:@"<P CLASS=" intoString:nil];
267         if ([sc scanString:@"<P CLASS=" intoString:nil])
268                 [sc scanUpToString:@">" intoString:&firstRes];
269         else
270                 firstRes = @"noClass";
271
272         secondLoc = [str length] * .9;
273         [sc setScanLocation:secondLoc];
274
275         [sc scanUpToString:@"<P CLASS=" intoString:nil];
276         if ([sc scanString:@"<P CLASS=" intoString:nil])
277                 [sc scanUpToString:@">" intoString:&secondRes];
278         else
279                 secondRes = @"noClass";
280
281         if ([firstRes isEqualToString:secondRes])
282                 secondRes = @"noClass";
283
284         subArray = [NSArray arrayWithObjects:firstRes, secondRes, nil];
285
286         return subArray;
287 }
288
289 static int parse_P(NSString *str, NSArray *subArray)
290 {
291         NSScanner *sc = [NSScanner scannerWithString:str];
292
293         NSString *res;
294         int subLang;
295
296         if ([sc scanString:@"CLASS=" intoString:nil])
297                 [sc scanUpToString:@">" intoString:&res];
298         else
299                 res = @"noClass";
300
301         if ([res isEqualToString:[subArray objectAtIndex:0]])
302                 subLang = 1;
303         else if ([res isEqualToString:[subArray objectAtIndex:1]])
304                 subLang = 2;
305         else
306                 subLang = 3;
307
308         return subLang;
309 }
310
311 static NSString *parse_COLOR(NSString *str)
312 {
313         NSString *cvalue;
314         NSMutableString *cname = [NSMutableString stringWithFormat:@"%@", str];
315
316         if ([cname characterAtIndex:0] == '#' && [cname lengthOfBytesUsingEncoding:NSASCIIStringEncoding] == 7)
317                 cvalue = [NSString stringWithFormat:@"{\\1c&H%@%@%@&}", [cname substringWithRange:NSMakeRange(5,2)], [cname substringWithRange:NSMakeRange(3,2)], [cname substringWithRange:NSMakeRange(1,2)]];
318         else {
319                 [cname replaceOccurrencesOfString:@"Aqua" withString:@"00FFFF" options:1 range:NSMakeRange(0,[cname length])];
320                 [cname replaceOccurrencesOfString:@"Black" withString:@"000000" options:1 range:NSMakeRange(0,[cname length])];
321                 [cname replaceOccurrencesOfString:@"Blue" withString:@"0000FF" options:1 range:NSMakeRange(0,[cname length])];
322                 [cname replaceOccurrencesOfString:@"Fuchsia" withString:@"FF00FF" options:1 range:NSMakeRange(0,[cname length])];
323                 [cname replaceOccurrencesOfString:@"Gray" withString:@"808080" options:1 range:NSMakeRange(0,[cname length])];
324                 [cname replaceOccurrencesOfString:@"Green" withString:@"008000" options:1 range:NSMakeRange(0,[cname length])];
325                 [cname replaceOccurrencesOfString:@"Lime" withString:@"00FF00" options:1 range:NSMakeRange(0,[cname length])];
326                 [cname replaceOccurrencesOfString:@"Maroon" withString:@"800000" options:1 range:NSMakeRange(0,[cname length])];
327                 [cname replaceOccurrencesOfString:@"Navy" withString:@"000080" options:1 range:NSMakeRange(0,[cname length])];
328                 [cname replaceOccurrencesOfString:@"Olive" withString:@"808000" options:1 range:NSMakeRange(0,[cname length])];
329                 [cname replaceOccurrencesOfString:@"Purple" withString:@"800080" options:1 range:NSMakeRange(0,[cname length])];
330                 [cname replaceOccurrencesOfString:@"Red" withString:@"FF0000" options:1 range:NSMakeRange(0,[cname length])];
331                 [cname replaceOccurrencesOfString:@"Silver" withString:@"C0C0C0" options:1 range:NSMakeRange(0,[cname length])];
332                 [cname replaceOccurrencesOfString:@"Teal" withString:@"008080" options:1 range:NSMakeRange(0,[cname length])];
333                 [cname replaceOccurrencesOfString:@"White" withString:@"FFFFFF" options:1 range:NSMakeRange(0,[cname length])];
334                 [cname replaceOccurrencesOfString:@"Yellow" withString:@"FFFF00" options:1 range:NSMakeRange(0,[cname length])];
335
336                 if ([cname lengthOfBytesUsingEncoding:NSASCIIStringEncoding] == 6)
337                         cvalue = [NSString stringWithFormat:@"{\\1c&H%@%@%@&}", [cname substringWithRange:NSMakeRange(4,2)], [cname substringWithRange:NSMakeRange(2,2)], [cname substringWithRange:NSMakeRange(0,2)]];
338                 else
339                         cvalue = @"{\\1c&HFFFFFF&}";
340         }
341
342         return cvalue;
343 }
344
345 static NSString *parse_FONT(NSString *str)
346 {
347         NSScanner *sc = [NSScanner scannerWithString:str];
348
349         NSString *res;
350         NSString *color;
351
352         if ([sc scanString:@"COLOR=" intoString:nil]) {
353                 [sc scanUpToString:@">" intoString:&res];
354                 color = parse_COLOR(res);
355         }
356         else
357                 color = @"{\\1c&HFFFFFF&}";
358
359         return color;
360 }
361
362 static NSMutableString *StandardizeSMIWhitespace(NSString *str)
363 {
364         if (!str) return nil;
365         NSMutableString *ms = [NSMutableString stringWithString:str];
366         [ms replaceOccurrencesOfString:@"\r" withString:@"" options:0 range:NSMakeRange(0,[ms length])];
367         [ms replaceOccurrencesOfString:@"\n" withString:@"" options:0 range:NSMakeRange(0,[ms length])];
368         [ms replaceOccurrencesOfString:@"&nbsp;" withString:@" " options:0 range:NSMakeRange(0,[ms length])];
369         return ms;
370 }
371
372 static void LoadSMIFromPath(NSString *path, SubSerializer *ss, int subCount)
373 {
374         NSMutableString *smi = StandardizeSMIWhitespace(STLoadFileWithUnknownEncoding(path));
375         if (!smi) return;
376                
377         NSScanner *sc = [NSScanner scannerWithString:smi];
378         NSString *res = nil;
379         [sc setCharactersToBeSkipped:nil];
380         [sc setCaseSensitive:NO];
381        
382         NSMutableString *cmt = [NSMutableString stringWithFormat:@""];
383         NSArray *subLanguage = parse_STYLE(smi);
384
385         int startTime=-1, endTime=-1, syncTime=-1;
386         int cc=1;
387        
388         enum {
389                 TAG_INIT,
390                 TAG_SYNC,
391                 TAG_P,
392                 TAG_BR_OPEN,
393                 TAG_BR_CLOSE,
394                 TAG_B_OPEN,
395                 TAG_B_CLOSE,
396                 TAG_I_OPEN,
397                 TAG_I_CLOSE,
398                 TAG_FONT_OPEN,
399                 TAG_FONT_CLOSE,
400                 TAG_COMMENT
401         } state = TAG_INIT;
402        
403         do {
404                 switch (state) {
405                         case TAG_INIT:
406                                 [sc scanUpToString:@"<SYNC" intoString:nil];
407                                 if ([sc scanString:@"<SYNC" intoString:nil])
408                                         state = TAG_SYNC;
409                                 break;
410                         case TAG_SYNC:
411                                 [sc scanUpToString:@">" intoString:&res];
412                                 syncTime = parse_SYNC(res);
413                                 if (startTime > -1) {
414                                         endTime = syncTime;
415                                         if (subCount == 2 && cc == 2)
416                                                 [cmt insertString:@"{\\an8}" atIndex:0];
417                                         if (subCount == 1 && cc == 1 || subCount == 2 && cc == 2) {
418                                                 SubLine *sl = [[SubLine alloc] initWithLine:cmt start:startTime end:endTime];
419                                                 [ss addLine:[sl autorelease]];
420                                         }
421                                 }
422                                 startTime = syncTime;
423                                 [cmt setString:@""];
424                                 state = TAG_COMMENT;
425                                 break;
426                         case TAG_P:
427                                 [sc scanUpToString:@">" intoString:&res];
428                                 cc = parse_P(res, subLanguage);
429                                 [cmt setString:@""];
430                                 state = TAG_COMMENT;
431                                 break;
432                         case TAG_BR_OPEN:
433                                 [sc scanUpToString:@">" intoString:nil];
434                                 [cmt appendString:@"\\n"];
435                                 state = TAG_COMMENT;
436                                 break;
437                         case TAG_BR_CLOSE:
438                                 [sc scanUpToString:@">" intoString:nil];
439                                 [cmt appendString:@"\\n"];
440                                 state = TAG_COMMENT;
441                                 break;
442                         case TAG_B_OPEN:
443                                 [sc scanUpToString:@">" intoString:&res];
444                                 [cmt appendString:@"{\\b1}"];
445                                 state = TAG_COMMENT;
446                                 break;
447                         case TAG_B_CLOSE:
448                                 [sc scanUpToString:@">" intoString:nil];
449                                 [cmt appendString:@"{\\b0}"];
450                                 state = TAG_COMMENT;
451                                 break;
452                         case TAG_I_OPEN:
453                                 [sc scanUpToString:@">" intoString:&res];
454                                 [cmt appendString:@"{\\i1}"];
455                                 state = TAG_COMMENT;
456                                 break;
457                         case TAG_I_CLOSE:
458                                 [sc scanUpToString:@">" intoString:nil];
459                                 [cmt appendString:@"{\\i0}"];
460                                 state = TAG_COMMENT;
461                                 break;
462                         case TAG_FONT_OPEN:
463                                 [sc scanUpToString:@">" intoString:&res];
464                                 [cmt appendString:parse_FONT(res)];
465                                 state = TAG_COMMENT;
466                                 break;
467                         case TAG_FONT_CLOSE:
468                                 [sc scanUpToString:@">" intoString:nil];
469                                 [cmt appendString:@"{\\1c&HFFFFFF&}"];
470                                 state = TAG_COMMENT;
471                                 break;
472                         case TAG_COMMENT:
473                                 [sc scanString:@">" intoString:nil];
474                                 if ([sc scanUpToString:@"<" intoString:&res])
475                                         [cmt appendString:res];
476                                 else
477                                         [cmt appendString:@"<>"];
478                                 if ([sc scanString:@"<" intoString:nil]) {
479                                         if ([sc scanString:@"SYNC" intoString:nil]) {
480                                                 state = TAG_SYNC;
481                                                 break;
482                                         }
483                                         else if ([sc scanString:@"P" intoString:nil]) {
484                                                 state = TAG_P;
485                                                 break;
486                                         }
487                                         else if ([sc scanString:@"BR" intoString:nil]) {
488                                                 state = TAG_BR_OPEN;
489                                                 break;
490                                         }
491                                         else if ([sc scanString:@"/BR" intoString:nil]) {
492                                                 state = TAG_BR_CLOSE;
493                                                 break;
494                                         }
495                                         else if ([sc scanString:@"B" intoString:nil]) {
496                                                 state = TAG_B_OPEN;
497                                                 break;
498                                         }
499                                         else if ([sc scanString:@"/B" intoString:nil]) {
500                                                 state = TAG_B_CLOSE;
501                                                 break;
502                                         }
503                                         else if ([sc scanString:@"I" intoString:nil]) {
504                                                 state = TAG_I_OPEN;
505                                                 break;
506                                         }
507                                         else if ([sc scanString:@"/I" intoString:nil]) {
508                                                 state = TAG_I_CLOSE;
509                                                 break;
510                                         }
511                                         else if ([sc scanString:@"FONT" intoString:nil]) {
512                                                 state = TAG_FONT_OPEN;
513                                                 break;
514                                         }
515                                         else if ([sc scanString:@"/FONT" intoString:nil]) {
516                                                 state = TAG_FONT_CLOSE;
517                                                 break;
518                                         }
519                                         else {
520                                                 [cmt appendString:@"<"];
521                                                 state = TAG_COMMENT;
522                                                 break;
523                                         }
524                                 }
525                 }
526         } while (![sc isAtEnd]);
527 }
528
529 static ComponentResult LoadSingleTextSubtitle(const FSRef *theDirectory, CFStringRef filename, Movie theMovie, Track *firstSubTrack, int subtitleType, int whichTrack)
530 {
531         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
532         UInt8 path[PATH_MAX];
533        
534         FSRefMakePath(theDirectory, path, PATH_MAX);
535         NSString *nsPath = [[NSString stringWithUTF8String:(char*)path] stringByAppendingPathComponent:(NSString*)filename];
536        
537         SubSerializer *ss = [[SubSerializer alloc] init];
538        
539         FourCharCode subCodec;
540         Handle header = NULL;
541         TimeScale timeBase;
542         Fixed movieRate = fixed1;
543        
544         switch (subtitleType) {
545                 case kSubTypeASS:
546                 case kSubTypeSSA:
547                 default:
548                 {
549                         timeBase = 100;
550                         subCodec = kSubFormatSSA;
551                         const char *cheader = [LoadSSAFromPath(nsPath, ss) UTF8String];
552                         int headerLen = strlen(cheader);
553                         PtrToHand(cheader, &header, headerLen);
554                 }
555                         break;
556                 case kSubTypeSRT:
557                         timeBase = 1000;
558                         subCodec = kSubFormatUTF8;
559                         LoadSRTFromPath(nsPath, ss);
560                         break;
561                 case kSubTypeSMI:
562                         timeBase = 1000;
563                         subCodec = kSubFormatUTF8;
564                         LoadSMIFromPath(nsPath, ss, whichTrack);
565                         break;
566         }
567        
568         [ss setFinished:YES];
569
570         SubPrerollFromHeader(header ? *header : NULL, header ? GetHandleSize(header) : 0);
571        
572         Handle dataRefHndl = NewHandleClear(sizeof(Handle) + 1);
573         UInt32 emptyDataRefExt[2] = {EndianU32_NtoB(sizeof(UInt32)*2), EndianU32_NtoB(kDataRefExtensionInitializationData)};
574         PtrAndHand(emptyDataRefExt, dataRefHndl, sizeof(emptyDataRefExt));
575        
576         Rect movieBox;
577         GetMovieBox(theMovie, &movieBox);
578        
579         ImageDescriptionHandle textDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription));
580
581         Track theTrack = CreatePlaintextSubTrack(theMovie, textDesc, timeBase, dataRefHndl, HandleDataHandlerSubType, subCodec, header, movieBox);
582         Media theMedia = NULL;
583         ComponentResult err=noErr;
584         TimeScale movieTimeScale = GetMovieTimeScale(theMovie);
585        
586         if (theTrack == NULL) {
587                 err = GetMoviesError();
588                 goto bail;
589         }
590        
591         theMedia = GetTrackMedia(theTrack);
592         if (theMedia == NULL) {
593                 err = GetMoviesError();
594                 goto bail;
595         }
596        
597         BeginMediaEdits(theMedia);
598        
599         while (![ss isEmpty]) {
600                 SubLine *sl = [ss getSerializedPacket];
601                 TimeRecord startTime = {SInt64ToWide(sl->begin_time), timeBase, 0};
602                 TimeValue sampleTime;
603                 const char *str = [sl->line UTF8String];
604                 unsigned sampleLen = strlen(str);
605                 Handle sampleHndl;
606                
607                 PtrToHand(str, &sampleHndl, sampleLen);
608                 err = AddMediaSample(theMedia, sampleHndl, 0, sampleLen, sl->end_time - sl->begin_time, (SampleDescriptionHandle)textDesc, 1, 0, &sampleTime);
609                
610                 if (err) {
611                         err = GetMoviesError();
612                         Codecprintf(stderr,"error %d adding line from %d to %d in external subtitles",err,sl->begin_time,sl->end_time);
613                 } else {
614                         ConvertTimeScale(&startTime, movieTimeScale);
615                         InsertMediaIntoTrack(theTrack, startTime.value.lo, sampleTime, sl->end_time - sl->begin_time, movieRate);
616                 }
617                
618                 err = noErr;
619                 DisposeHandle(sampleHndl);
620         }
621        
622         EndMediaEdits(theMedia);
623        
624         if (*firstSubTrack == NULL)
625                 *firstSubTrack = theTrack;
626         else
627                 SetTrackAlternate(*firstSubTrack, theTrack);
628        
629         SetMediaLanguage(theMedia, GetFilenameLanguage(filename));
630        
631 bail:
632         [ss release];
633         [pool release];
634        
635         if (err) {
636                 if (theMedia)
637                         DisposeTrackMedia(theMedia);
638                
639                 if (theTrack)
640                         DisposeMovieTrack(theTrack);
641         }
642        
643         if (textDesc)    DisposeHandle((Handle)textDesc);
644         if (header)      DisposeHandle((Handle)header);
645         if (dataRefHndl) DisposeHandle((Handle)dataRefHndl);
646        
647         return err;
648 }
649
650 static ComponentResult LoadVobSubSubtitles(const FSRef *theDirectory, CFStringRef filename, Movie theMovie, Track *firstSubTrack)
651 {
652         ComponentResult err = noErr;
653        
654         return err;
655 }
656
657 static Boolean ShouldLoadExternalSubtitles()
658 {
659         Boolean isSet, value;
660        
661         value = CFPreferencesGetAppBooleanValue(CFSTR("LoadExternalSubtitles"),CFSTR("org.perian.Perian"),&isSet);
662        
663         return isSet ? value : YES;
664 }
665
666 ComponentResult LoadExternalSubtitles(const FSRef *theFile, Movie theMovie)
667 {
668         ComponentResult err = noErr;
669         Track firstSubTrack = NULL;
670         HFSUniStr255 hfsFilename;
671         CFStringRef cfFilename = NULL;
672         FSRef parentDir;
673         FSIterator dirItr = NULL;
674         CFRange baseFilenameRange;
675         ItemCount filesFound;
676         Boolean containerChanged;
677        
678         if (!ShouldLoadExternalSubtitles()) return noErr;
679        
680         err = FSGetCatalogInfo(theFile, kFSCatInfoNone, NULL, &hfsFilename, NULL, &parentDir);
681         if (err) goto bail;
682        
683         // find the location of the extension
684         cfFilename = CFStringCreateWithCharacters(NULL, hfsFilename.unicode, hfsFilename.length);
685         baseFilenameRange = CFStringFind(cfFilename, CFSTR("."), kCFCompareBackwards);
686        
687         // strip the extension
688         if (baseFilenameRange.location != kCFNotFound) {
689                 CFStringRef temp = cfFilename;
690                 baseFilenameRange.length = baseFilenameRange.location;
691                 baseFilenameRange.location = 0;
692                 cfFilename = CFStringCreateWithSubstring(NULL, temp, baseFilenameRange);
693                 CFRelease(temp);
694         }
695        
696         err = FSOpenIterator(&parentDir, kFSIterateFlat, &dirItr);
697         if (err) goto bail;
698        
699         do {
700                 FSRef foundFileRef;
701                 HFSUniStr255 hfsFoundFilename;
702                 CFStringRef cfFoundFilename = NULL;
703                 CFComparisonResult cmpRes;
704                
705                 err = FSGetCatalogInfoBulk(dirItr, 1, &filesFound, &containerChanged, kFSCatInfoNone,
706                                                    NULL, &foundFileRef, NULL, &hfsFoundFilename);
707                 if (err) goto bail;
708                
709                 cfFoundFilename = CFStringCreateWithCharacters(NULL, hfsFoundFilename.unicode, hfsFoundFilename.length);
710                 cmpRes = CFStringCompareWithOptions(cfFoundFilename, cfFilename, baseFilenameRange, kCFCompareCaseInsensitive);
711                
712                 // two files share the same base, so now check the extension of the found file
713                 if (cmpRes == kCFCompareEqualTo) {
714                         CFRange extRange = { CFStringGetLength(cfFoundFilename) - 4, 4 };
715                         CFRange actRange;
716                         int subType = -1;
717                        
718                         // SubRip
719                         actRange = CFStringFind(cfFoundFilename, CFSTR(".srt"), kCFCompareCaseInsensitive | kCFCompareBackwards);
720                         if (actRange.length && actRange.location == extRange.location)
721                                 subType = kSubTypeSRT;
722                         else {
723                                 // SubStationAlpha
724                                 actRange = CFStringFind(cfFoundFilename, CFSTR(".ass"), kCFCompareCaseInsensitive | kCFCompareBackwards);
725                                 if (actRange.length && actRange.location == extRange.location)
726                                         subType = kSubTypeASS;
727                                 else {
728                                         actRange = CFStringFind(cfFoundFilename, CFSTR(".ssa"), kCFCompareCaseInsensitive | kCFCompareBackwards);
729                                         if (actRange.length && actRange.location == extRange.location)
730                                                 subType = kSubTypeSSA;
731                                         else {
732                                                 // VobSub
733                                                 actRange = CFStringFind(cfFoundFilename, CFSTR(".idx"), kCFCompareCaseInsensitive | kCFCompareBackwards);
734                                                 if (actRange.length && actRange.location == extRange.location)
735                                                         err = LoadVobSubSubtitles(&parentDir, cfFoundFilename, theMovie, &firstSubTrack);
736                                                 else {
737                                                         actRange = CFStringFind(cfFoundFilename, CFSTR(".smi"), kCFCompareCaseInsensitive | kCFCompareBackwards);
738                                                         if (actRange.length && actRange.location == extRange.location)
739                                                                 subType = kSubTypeSMI;
740                                                 }
741                                         }
742                                 }
743                         }
744                        
745                         if (subType == kSubTypeSMI) {
746                                 err = LoadSingleTextSubtitle(&parentDir, cfFoundFilename, theMovie, &firstSubTrack, kSubTypeSMI, 1);
747                                 if (!err) err = LoadSingleTextSubtitle(&parentDir, cfFoundFilename, theMovie, &firstSubTrack, kSubTypeSMI, 2);
748                         }
749                         else if (subType != -1)
750                                 err = LoadSingleTextSubtitle(&parentDir, cfFoundFilename, theMovie, &firstSubTrack, subType, 0);
751
752                         if (err) goto bail;
753                 }
754                
755                 CFRelease(cfFoundFilename);
756         } while (filesFound && !containerChanged);
757        
758 bail:   
759                 if (err == errFSNoMoreItems)
760                         err = noErr;
761        
762         if (dirItr)
763                 FSCloseIterator(dirItr);
764        
765         if (cfFilename)
766                 CFRelease(cfFilename);
767        
768         return err;
769 }
770
771 ComponentResult LoadExternalSubtitlesFromFileDataRef(Handle dataRef, OSType dataRefType, Movie theMovie)
772 {
773         if (dataRefType != AliasDataHandlerSubType) return noErr;
774        
775         CFStringRef cfPath;
776         FSRef ref;
777         uint8_t path[PATH_MAX];
778        
779         QTGetDataReferenceFullPathCFString(dataRef, dataRefType, kQTPOSIXPathStyle, &cfPath);
780         CFStringGetCString(cfPath, (char*)path, PATH_MAX, kCFStringEncodingUTF8);
781         FSPathMakeRef(path, &ref, NULL);
782         CFRelease(cfPath);
783        
784         return LoadExternalSubtitles(&ref, theMovie);
785 }
786
787 #pragma mark Obj-C Classes
788
789 @implementation SubSerializer
790 -(id)init
791 {
792         if (self = [super init]) {
793                 lines = [[NSMutableArray alloc] init];
794                 outpackets = [[NSMutableArray alloc] init];
795                 finished = NO;
796                 write_gap = NO;
797                 toReturn = nil;
798                 last_time = 0;
799         }
800        
801         return self;
802 }
803
804 -(void)dealloc
805 {
806         [outpackets release];
807         [lines release];
808         [super dealloc];
809 }
810
811 -(void)addLine:(SubLine *)sline
812 {
813         if (sline->begin_time < sline->end_time)
814                 [lines addObject:sline];
815 }
816
817 static int cmp_line(id a, id b, void* unused)
818 {                       
819         SubLine *av = (SubLine*)a, *bv = (SubLine*)b;
820        
821         if (av->begin_time > bv->begin_time) return NSOrderedDescending;
822         if (av->begin_time < bv->begin_time) return NSOrderedAscending;
823         return NSOrderedSame;
824 }
825
826 static int cmp_uint(const void *a, const void *b)
827 {
828         unsigned av = *(unsigned*)a, bv = *(unsigned*)b;
829        
830         if (av > bv) return 1;
831         if (av < bv) return -1;
832         return 0;
833 }
834
835 static bool isinrange(unsigned base, unsigned test_s, unsigned test_e)
836 {
837         return (base >= test_s) && (base < test_e);
838 }
839
840 -(void)refill
841 {
842         unsigned num = [lines count];
843         unsigned min_allowed = finished ? 1 : 2;
844         if (num < min_allowed) return;
845         unsigned times[num*2], last_last_end = 0;
846         SubLine *slines[num], *last=nil;
847         bool last_has_invalid_end = false;
848        
849         [lines sortUsingFunction:cmp_line context:nil];
850         [lines getObjects:slines];
851 #ifdef SS_DEBUG
852         NSLog(@"pre - %@",lines);
853 #endif 
854         //leave early if all subtitle lines overlap
855         if (!finished) {
856                 bool all_overlap = true;
857                 int i;
858                
859                 for (i=0;i < num-1;i++) {
860                         SubLine *c = slines[i], *n = slines[i+1];
861                         if (c->end_time <= n->begin_time) {all_overlap = false; break;}
862                 }
863                
864                 if (all_overlap) return;
865                
866                 for (i=0;i < num-1;i++) {
867                         if (isinrange(slines[num-1]->begin_time, slines[i]->begin_time, slines[i]->end_time)) {
868                                 num = i + 1; break;
869                         }
870                 }
871         }
872        
873         for (int i=0;i < num;i++) {
874                 times[i*2]   = slines[i]->begin_time;
875                 times[i*2+1] = slines[i]->end_time;
876         }
877        
878         qsort(times, num*2, sizeof(unsigned), cmp_uint);
879        
880         for (int i=0;i < num*2; i++) {
881                 if (i > 0 && times[i-1] == times[i]) continue;
882                 NSMutableString *accum = nil;
883                 unsigned start = times[i], la