source: trunk/Subtitles/SubImport.mm @ 1044

Revision 1044, 42.0 KB checked in by astrange, 5 years ago (diff)
  • Add new tool targets ssa2html and detect-encoding.
  • Lift some Xcode settings out of the Perian target into the project.
  • Fix compilation error in FFission.
  • Remove unused variable in SubContext?. (small memory savings)
Line 
1/*
2 * SubImport.mm
3 * Created by Alexander Strange on 7/24/07.
4 *
5 * This file is part of Perian.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#include <QuickTime/QuickTime.h>
23#include "CommonUtils.h"
24#include "Codecprintf.h"
25#include "CodecIDs.h"
26#import "SubImport.h"
27#import "SubParsing.h"
28#import "SubUtilities.h"
29
30//#define SS_DEBUG
31
32extern "C" {
33        int ExtractVobSubPacket(UInt8 *dest, const UInt8 *framedSrc, int srcSize, int *usedSrcBytes);
34}
35
36#pragma mark C
37
38// if the subtitle filename is something like title.en.srt or movie.fre.srt
39// this function detects it and returns the subtitle language
40short GetFilenameLanguage(CFStringRef filename)
41{
42        CFRange findResult;
43        CFStringRef baseName = NULL;
44        CFStringRef langStr = NULL;
45        short lang = langUnspecified;
46       
47        // find and strip the extension
48        findResult = CFStringFind(filename, CFSTR("."), kCFCompareBackwards);
49        findResult.length = findResult.location;
50        findResult.location = 0;
51        baseName = CFStringCreateWithSubstring(NULL, filename, findResult);
52       
53        // then find the previous period
54        findResult = CFStringFind(baseName, CFSTR("."), kCFCompareBackwards);
55        findResult.location++;
56        findResult.length = CFStringGetLength(baseName) - findResult.location;
57       
58        // check for 3 char language code
59        if (findResult.length == 3) {
60                char langCStr[4] = "";
61               
62                langStr = CFStringCreateWithSubstring(NULL, baseName, findResult);
63                CFStringGetCString(langStr, langCStr, 4, kCFStringEncodingASCII);
64                lang = ISO639_2ToQTLangCode(langCStr);
65               
66                CFRelease(langStr);
67               
68                // and for a 2 char language code
69        } else if (findResult.length == 2) {
70                char langCStr[3] = "";
71               
72                langStr = CFStringCreateWithSubstring(NULL, baseName, findResult);
73                CFStringGetCString(langStr, langCStr, 3, kCFStringEncodingASCII);
74                lang = ISO639_1ToQTLangCode(langCStr);
75               
76                CFRelease(langStr);
77        }
78       
79        CFRelease(baseName);
80        return lang;
81}
82
83//Use ugly transparency ("transparent" blend mode) for files imported in Front Row
84//At the moment it doesn't support graphicsModePreBlackAlpha
85static bool ShouldEngageFrontRowHack(void)
86{
87        bool ret;
88        Boolean isSet;
89       
90        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
91        NSString *applicationName = [[NSProcessInfo processInfo] processName];
92        long minorVersion;
93        Gestalt(gestaltSystemVersionMinor, &minorVersion);
94        if (!CFPreferencesGetAppBooleanValue(CFSTR("PerianFrontRowSubtitleHack"),CFSTR("org.perian.Perian"),&isSet))
95                isSet = 1;
96       
97        ret = (minorVersion == 5) && [applicationName isEqualToString:@"Front Row"] && isSet;
98        [pool release];
99       
100        return ret;
101}
102
103Track CreatePlaintextSubTrack(Movie theMovie, ImageDescriptionHandle imgDesc,
104                              TimeScale timescale, Handle dataRef, OSType dataRefType, FourCharCode subType, Handle imageExtension, Rect movieBox)
105{
106        Fixed trackWidth, trackHeight;
107        Track theTrack;
108        Media theMedia;
109       
110        // plain text subs have no size on their own
111        trackWidth = IntToFixed(movieBox.right - movieBox.left);
112        trackHeight = IntToFixed(movieBox.bottom - movieBox.top);
113       
114        (*imgDesc)->idSize = sizeof(ImageDescription);
115        (*imgDesc)->cType = subType;
116        (*imgDesc)->width = FixedToInt(trackWidth);
117        (*imgDesc)->height = FixedToInt(trackHeight);
118        (*imgDesc)->frameCount = 1;
119        (*imgDesc)->depth = 32;
120        (*imgDesc)->clutID = -1;
121       
122        if (!trackWidth || !trackHeight) {trackWidth = IntToFixed(640); trackHeight = IntToFixed(480);}
123       
124        if (imageExtension) AddImageDescriptionExtension(imgDesc, imageExtension, subType);
125       
126        theTrack = NewMovieTrack(theMovie, trackWidth, trackHeight, kNoVolume);
127        if (theTrack != NULL) {
128                theMedia = NewTrackMedia(theTrack, VideoMediaType, timescale, dataRef, dataRefType);
129               
130                if (theMedia != NULL) {
131                        // finally, say that we're transparent
132                        MediaHandler mh = GetMediaHandler(theMedia);
133                       
134                        if (ShouldEngageFrontRowHack()) {
135                                RGBColor blendColor = {0x0000, 0x0000, 0x0000};
136                                MediaSetGraphicsMode(mh, transparent, &blendColor);
137                        }
138                        else MediaSetGraphicsMode(mh, graphicsModePreBlackAlpha, NULL);
139                       
140                        // subtitle tracks should be above the video track, which should be layer 0
141                        SetTrackLayer(theTrack, -1);
142                } else {
143                        DisposeMovieTrack(theTrack);
144                        theTrack = NULL;
145                }
146        }
147       
148        return theTrack;
149}
150
151static NSString *MatroskaPacketizeLine(NSDictionary *sub, int n)
152{
153        NSString *name = [sub objectForKey:@"Name"];
154        if (!name) name = [sub objectForKey:@"Actor"];
155       
156        return [NSString stringWithFormat:@"%d,%d,%@,%@,%@,%@,%@,%@,%@\n",
157                n+1,
158                [[sub objectForKey:@"Layer"] intValue],
159                [sub objectForKey:@"Style"],
160                [sub objectForKey:@"Name"],
161                [sub objectForKey:@"MarginL"],
162                [sub objectForKey:@"MarginR"],
163                [sub objectForKey:@"MarginV"],
164                [sub objectForKey:@"Effect"],
165                [sub objectForKey:@"Text"]];
166}
167
168static unsigned ParseSubTime(const char *time, unsigned secondScale, BOOL hasSign)
169{
170        unsigned hour, minute, second, subsecond, timeval;
171        char separator;
172        int sign = 1;
173       
174        if (hasSign && *time == '-') {
175                sign = -1;
176                time++;
177        }
178       
179        if (sscanf(time,"%u:%u:%u%[,.:]%u",&hour,&minute,&second,&separator,&subsecond) < 5)
180                return 0;
181       
182        timeval = hour * 60 * 60 + minute * 60 + second;
183        timeval = secondScale * timeval + subsecond;
184       
185        return timeval * sign;
186}
187
188NSString *LoadSSAFromPath(NSString *path, SubSerializer *ss)
189{
190        NSString *nssSub = STLoadFileWithUnknownEncoding(path);
191       
192        if (!nssSub) return nil;
193       
194        size_t slen = [nssSub length], flen = sizeof(unichar) * (slen+1);
195        unichar *subdata = (unichar*)malloc(flen);
196        [nssSub getCharacters:subdata];
197       
198        if (subdata[slen-1] != '\n') subdata[slen++] = '\n'; // append newline if missing
199       
200        NSDictionary *headers;
201        NSArray *subs;
202       
203        SubParseSSAFile(subdata, slen, &headers, NULL, &subs);
204        free(subdata);
205       
206        int i, numlines = [subs count];
207       
208        for (i = 0; i < numlines; i++) {
209                NSDictionary *sub = [subs objectAtIndex:i];
210                SubLine *sl = [[SubLine alloc] initWithLine:MatroskaPacketizeLine(sub, i)
211                                                                                          start:ParseSubTime([[sub objectForKey:@"Start"] UTF8String],100,NO)
212                                                                                                end:ParseSubTime([[sub objectForKey:@"End"] UTF8String],100,NO)];
213               
214                [ss addLine:sl];
215                [sl autorelease];
216        }
217               
218        return [nssSub substringToIndex:[nssSub rangeOfString:@"[Events]" options:NSLiteralSearch].location];
219}
220
221#pragma mark SAMI Parsing
222
223static void LoadSRTFromPath(NSString *path, SubSerializer *ss)
224{
225        NSMutableString *srt = STStandardizeStringNewlines(STLoadFileWithUnknownEncoding(path));
226        if (!srt) return;
227               
228        if ([srt characterAtIndex:0] == 0xFEFF) [srt deleteCharactersInRange:NSMakeRange(0,1)];
229        if ([srt characterAtIndex:[srt length]-1] != '\n') [srt appendFormat:@"%c",'\n'];
230       
231        NSScanner *sc = [NSScanner scannerWithString:srt];
232        NSString *res=nil;
233        [sc setCharactersToBeSkipped:nil];
234       
235        unsigned startTime=0, endTime=0;
236       
237        enum {
238                INITIAL,
239                TIMESTAMP,
240                LINES
241        } state = INITIAL;
242       
243        do {
244                switch (state) {
245                        case INITIAL:
246                                if ([sc scanInt:NULL] == TRUE && [sc scanUpToString:@"\n" intoString:&res] == FALSE) {
247                                        state = TIMESTAMP;
248                                        [sc scanString:@"\n" intoString:nil];
249                                } else
250                                        [sc setScanLocation:[sc scanLocation]+1];
251                                break;
252                        case TIMESTAMP:
253                                [sc scanUpToString:@" --> " intoString:&res];
254                                [sc scanString:@" --> " intoString:nil];
255                                startTime = ParseSubTime([res UTF8String], 1000, NO);
256                               
257                                [sc scanUpToString:@"\n" intoString:&res];
258                                [sc scanString:@"\n" intoString:nil];
259                                endTime = ParseSubTime([res UTF8String], 1000, NO);
260                                state = LINES;
261                                break;
262                        case LINES:
263                                [sc scanUpToString:@"\n\n" intoString:&res];
264                                [sc scanString:@"\n\n" intoString:nil];
265                                SubLine *sl = [[SubLine alloc] initWithLine:res start:startTime end:endTime];
266                                [ss addLine:[sl autorelease]];
267                                state = INITIAL;
268                                break;
269                };
270        } while (![sc isAtEnd]);
271}
272
273static int parse_SYNC(NSString *str)
274{
275        NSScanner *sc = [NSScanner scannerWithString:str];
276
277        int res;
278
279        if ([sc scanString:@"START=" intoString:nil])
280                [sc scanInt:&res];
281
282        return res;
283}
284
285static NSArray *parse_STYLE(NSString *str)
286{
287        NSScanner *sc = [NSScanner scannerWithString:str];
288
289        NSString *firstRes;
290        NSString *secondRes;
291        NSArray *subArray;
292        int secondLoc;
293
294        [sc scanUpToString:@"<P CLASS=" intoString:nil];
295        if ([sc scanString:@"<P CLASS=" intoString:nil])
296                [sc scanUpToString:@">" intoString:&firstRes];
297        else
298                firstRes = @"noClass";
299
300        secondLoc = [str length] * .9;
301        [sc setScanLocation:secondLoc];
302
303        [sc scanUpToString:@"<P CLASS=" intoString:nil];
304        if ([sc scanString:@"<P CLASS=" intoString:nil])
305                [sc scanUpToString:@">" intoString:&secondRes];
306        else
307                secondRes = @"noClass";
308
309        if ([firstRes isEqualToString:secondRes])
310                secondRes = @"noClass";
311
312        subArray = [NSArray arrayWithObjects:firstRes, secondRes, nil];
313
314        return subArray;
315}
316
317static int parse_P(NSString *str, NSArray *subArray)
318{
319        NSScanner *sc = [NSScanner scannerWithString:str];
320
321        NSString *res;
322        int subLang;
323
324        if ([sc scanString:@"CLASS=" intoString:nil])
325                [sc scanUpToString:@">" intoString:&res];
326        else
327                res = @"noClass";
328
329        if ([res isEqualToString:[subArray objectAtIndex:0]])
330                subLang = 1;
331        else if ([res isEqualToString:[subArray objectAtIndex:1]])
332                subLang = 2;
333        else
334                subLang = 3;
335
336        return subLang;
337}
338
339static NSString *parse_COLOR(NSString *str)
340{
341        NSString *cvalue;
342        NSMutableString *cname = [NSMutableString stringWithFormat:@"%@", str];
343
344        if ([cname characterAtIndex:0] == '#' && [cname lengthOfBytesUsingEncoding:NSASCIIStringEncoding] == 7)
345                cvalue = [NSString stringWithFormat:@"{\\1c&H%@%@%@&}", [cname substringWithRange:NSMakeRange(5,2)], [cname substringWithRange:NSMakeRange(3,2)], [cname substringWithRange:NSMakeRange(1,2)]];
346        else {
347                [cname replaceOccurrencesOfString:@"Aqua" withString:@"00FFFF" options:1 range:NSMakeRange(0,[cname length])];
348                [cname replaceOccurrencesOfString:@"Black" withString:@"000000" options:1 range:NSMakeRange(0,[cname length])];
349                [cname replaceOccurrencesOfString:@"Blue" withString:@"0000FF" options:1 range:NSMakeRange(0,[cname length])];
350                [cname replaceOccurrencesOfString:@"Fuchsia" withString:@"FF00FF" options:1 range:NSMakeRange(0,[cname length])];
351                [cname replaceOccurrencesOfString:@"Gray" withString:@"808080" options:1 range:NSMakeRange(0,[cname length])];
352                [cname replaceOccurrencesOfString:@"Green" withString:@"008000" options:1 range:NSMakeRange(0,[cname length])];
353                [cname replaceOccurrencesOfString:@"Lime" withString:@"00FF00" options:1 range:NSMakeRange(0,[cname length])];
354                [cname replaceOccurrencesOfString:@"Maroon" withString:@"800000" options:1 range:NSMakeRange(0,[cname length])];
355                [cname replaceOccurrencesOfString:@"Navy" withString:@"000080" options:1 range:NSMakeRange(0,[cname length])];
356                [cname replaceOccurrencesOfString:@"Olive" withString:@"808000" options:1 range:NSMakeRange(0,[cname length])];
357                [cname replaceOccurrencesOfString:@"Purple" withString:@"800080" options:1 range:NSMakeRange(0,[cname length])];
358                [cname replaceOccurrencesOfString:@"Red" withString:@"FF0000" options:1 range:NSMakeRange(0,[cname length])];
359                [cname replaceOccurrencesOfString:@"Silver" withString:@"C0C0C0" options:1 range:NSMakeRange(0,[cname length])];
360                [cname replaceOccurrencesOfString:@"Teal" withString:@"008080" options:1 range:NSMakeRange(0,[cname length])];
361                [cname replaceOccurrencesOfString:@"White" withString:@"FFFFFF" options:1 range:NSMakeRange(0,[cname length])];
362                [cname replaceOccurrencesOfString:@"Yellow" withString:@"FFFF00" options:1 range:NSMakeRange(0,[cname length])];
363
364                if ([cname lengthOfBytesUsingEncoding:NSASCIIStringEncoding] == 6)
365                        cvalue = [NSString stringWithFormat:@"{\\1c&H%@%@%@&}", [cname substringWithRange:NSMakeRange(4,2)], [cname substringWithRange:NSMakeRange(2,2)], [cname substringWithRange:NSMakeRange(0,2)]];
366                else
367                        cvalue = @"{\\1c&HFFFFFF&}";
368        }
369
370        return cvalue;
371}
372
373static NSString *parse_FONT(NSString *str)
374{
375        NSScanner *sc = [NSScanner scannerWithString:str];
376
377        NSString *res;
378        NSString *color;
379
380        if ([sc scanString:@"COLOR=" intoString:nil]) {
381                [sc scanUpToString:@">" intoString:&res];
382                color = parse_COLOR(res);
383        }
384        else
385                color = @"{\\1c&HFFFFFF&}";
386
387        return color;
388}
389
390static NSMutableString *StandardizeSMIWhitespace(NSString *str)
391{
392        if (!str) return nil;
393        NSMutableString *ms = [NSMutableString stringWithString:str];
394        [ms replaceOccurrencesOfString:@"\r" withString:@"" options:0 range:NSMakeRange(0,[ms length])];
395        [ms replaceOccurrencesOfString:@"\n" withString:@"" options:0 range:NSMakeRange(0,[ms length])];
396        [ms replaceOccurrencesOfString:@"&nbsp;" withString:@" " options:0 range:NSMakeRange(0,[ms length])];
397        return ms;
398}
399
400static void LoadSMIFromPath(NSString *path, SubSerializer *ss, int subCount)
401{
402        NSMutableString *smi = StandardizeSMIWhitespace(STLoadFileWithUnknownEncoding(path));
403        if (!smi) return;
404               
405        NSScanner *sc = [NSScanner scannerWithString:smi];
406        NSString *res = nil;
407        [sc setCharactersToBeSkipped:nil];
408        [sc setCaseSensitive:NO];
409       
410        NSMutableString *cmt = [NSMutableString stringWithFormat:@""];
411        NSArray *subLanguage = parse_STYLE(smi);
412
413        int startTime=-1, endTime=-1, syncTime=-1;
414        int cc=1;
415       
416        enum {
417                TAG_INIT,
418                TAG_SYNC,
419                TAG_P,
420                TAG_BR_OPEN,
421                TAG_BR_CLOSE,
422                TAG_B_OPEN,
423                TAG_B_CLOSE,
424                TAG_I_OPEN,
425                TAG_I_CLOSE,
426                TAG_FONT_OPEN,
427                TAG_FONT_CLOSE,
428                TAG_COMMENT
429        } state = TAG_INIT;
430       
431        do {
432                switch (state) {
433                        case TAG_INIT:
434                                [sc scanUpToString:@"<SYNC" intoString:nil];
435                                if ([sc scanString:@"<SYNC" intoString:nil])
436                                        state = TAG_SYNC;
437                                break;
438                        case TAG_SYNC:
439                                [sc scanUpToString:@">" intoString:&res];
440                                syncTime = parse_SYNC(res);
441                                if (startTime > -1) {
442                                        endTime = syncTime;
443                                        if (subCount == 2 && cc == 2)
444                                                [cmt insertString:@"{\\an8}" atIndex:0];
445                                        if (subCount == 1 && cc == 1 || subCount == 2 && cc == 2) {
446                                                SubLine *sl = [[SubLine alloc] initWithLine:cmt start:startTime end:endTime];
447                                                [ss addLine:[sl autorelease]];
448                                        }
449                                }
450                                startTime = syncTime;
451                                [cmt setString:@""];
452                                state = TAG_COMMENT;
453                                break;
454                        case TAG_P:
455                                [sc scanUpToString:@">" intoString:&res];
456                                cc = parse_P(res, subLanguage);
457                                [cmt setString:@""];
458                                state = TAG_COMMENT;
459                                break;
460                        case TAG_BR_OPEN:
461                                [sc scanUpToString:@">" intoString:nil];
462                                [cmt appendString:@"\\n"];
463                                state = TAG_COMMENT;
464                                break;
465                        case TAG_BR_CLOSE:
466                                [sc scanUpToString:@">" intoString:nil];
467                                [cmt appendString:@"\\n"];
468                                state = TAG_COMMENT;
469                                break;
470                        case TAG_B_OPEN:
471                                [sc scanUpToString:@">" intoString:&res];
472                                [cmt appendString:@"{\\b1}"];
473                                state = TAG_COMMENT;
474                                break;
475                        case TAG_B_CLOSE:
476                                [sc scanUpToString:@">" intoString:nil];
477                                [cmt appendString:@"{\\b0}"];
478                                state = TAG_COMMENT;
479                                break;
480                        case TAG_I_OPEN:
481                                [sc scanUpToString:@">" intoString:&res];
482                                [cmt appendString:@"{\\i1}"];
483                                state = TAG_COMMENT;
484                                break;
485                        case TAG_I_CLOSE:
486                                [sc scanUpToString:@">" intoString:nil];
487                                [cmt appendString:@"{\\i0}"];
488                                state = TAG_COMMENT;
489                                break;
490                        case TAG_FONT_OPEN:
491                                [sc scanUpToString:@">" intoString:&res];
492                                [cmt appendString:parse_FONT(res)];
493                                state = TAG_COMMENT;
494                                break;
495                        case TAG_FONT_CLOSE:
496                                [sc scanUpToString:@">" intoString:nil];
497                                [cmt appendString:@"{\\1c&HFFFFFF&}"];
498                                state = TAG_COMMENT;
499                                break;
500                        case TAG_COMMENT:
501                                [sc scanString:@">" intoString:nil];
502                                if ([sc scanUpToString:@"<" intoString:&res])
503                                        [cmt appendString:res];
504                                else
505                                        [cmt appendString:@"<>"];
506                                if ([sc scanString:@"<" intoString:nil]) {
507                                        if ([sc scanString:@"SYNC" intoString:nil]) {
508                                                state = TAG_SYNC;
509                                                break;
510                                        }
511                                        else if ([sc scanString:@"P" intoString:nil]) {
512                                                state = TAG_P;
513                                                break;
514                                        }
515                                        else if ([sc scanString:@"BR" intoString:nil]) {
516                                                state = TAG_BR_OPEN;
517                                                break;
518                                        }
519                                        else if ([sc scanString:@"/BR" intoString:nil]) {
520                                                state = TAG_BR_CLOSE;
521                                                break;
522                                        }
523                                        else if ([sc scanString:@"B" intoString:nil]) {
524                                                state = TAG_B_OPEN;
525                                                break;
526                                        }
527                                        else if ([sc scanString:@"/B" intoString:nil]) {
528                                                state = TAG_B_CLOSE;
529                                                break;
530                                        }
531                                        else if ([sc scanString:@"I" intoString:nil]) {
532                                                state = TAG_I_OPEN;
533                                                break;
534                                        }
535                                        else if ([sc scanString:@"/I" intoString:nil]) {
536                                                state = TAG_I_CLOSE;
537                                                break;
538                                        }
539                                        else if ([sc scanString:@"FONT" intoString:nil]) {
540                                                state = TAG_FONT_OPEN;
541                                                break;
542                                        }
543                                        else if ([sc scanString:@"/FONT" intoString:nil]) {
544                                                state = TAG_FONT_CLOSE;
545                                                break;
546                                        }
547                                        else {
548                                                [cmt appendString:@"<"];
549                                                state = TAG_COMMENT;
550                                                break;
551                                        }
552                                }
553                }
554        } while (![sc isAtEnd]);
555}
556
557static ComponentResult LoadSingleTextSubtitle(const FSRef *theDirectory, CFStringRef filename, Movie theMovie, Track *firstSubTrack, int subtitleType, int whichTrack)
558{
559        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
560        UInt8 path[PATH_MAX];
561       
562        FSRefMakePath(theDirectory, path, PATH_MAX);
563        NSString *nsPath = [[NSString stringWithUTF8String:(char*)path] stringByAppendingPathComponent:(NSString*)filename];
564       
565        SubSerializer *ss = [[SubSerializer alloc] init];
566       
567        FourCharCode subCodec;
568        Handle header = NULL;
569        TimeScale timeBase;
570        Fixed movieRate = fixed1;
571       
572        switch (subtitleType) {
573                case kSubTypeASS:
574                case kSubTypeSSA:
575                default:
576                {
577                        timeBase = 100;
578                        subCodec = kSubFormatSSA;
579                        const char *cheader = [LoadSSAFromPath(nsPath, ss) UTF8String];
580                        int headerLen = strlen(cheader);
581                        PtrToHand(cheader, &header, headerLen);
582                }
583                        break;
584                case kSubTypeSRT:
585                        timeBase = 1000;
586                        subCodec = kSubFormatUTF8;
587                        LoadSRTFromPath(nsPath, ss);
588                        break;
589                case kSubTypeSMI:
590                        timeBase = 1000;
591                        subCodec = kSubFormatUTF8;
592                        LoadSMIFromPath(nsPath, ss, whichTrack);
593                        break;
594        }
595       
596        [ss setFinished:YES];
597
598        SubPrerollFromHeader(header ? *header : NULL, header ? GetHandleSize(header) : 0);
599       
600        Handle dataRefHndl = NewHandleClear(sizeof(Handle) + 1);
601        UInt32 emptyDataRefExt[2] = {EndianU32_NtoB(sizeof(UInt32)*2), EndianU32_NtoB(kDataRefExtensionInitializationData)};
602        PtrAndHand(emptyDataRefExt, dataRefHndl, sizeof(emptyDataRefExt));
603       
604        Rect movieBox;
605        GetMovieBox(theMovie, &movieBox);
606       
607        ImageDescriptionHandle textDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription));
608
609        Track theTrack = CreatePlaintextSubTrack(theMovie, textDesc, timeBase, dataRefHndl, HandleDataHandlerSubType, subCodec, header, movieBox);
610        Media theMedia = NULL;
611        ComponentResult err=noErr;
612        TimeScale movieTimeScale = GetMovieTimeScale(theMovie);
613       
614        if (theTrack == NULL) {
615                err = GetMoviesError();
616                goto bail;
617        }
618       
619        theMedia = GetTrackMedia(theTrack);
620        if (theMedia == NULL) {
621                err = GetMoviesError();
622                goto bail;
623        }
624       
625        BeginMediaEdits(theMedia);
626       
627        while (![ss isEmpty]) {
628                SubLine *sl = [ss getSerializedPacket];
629                TimeRecord startTime = {SInt64ToWide(sl->begin_time), timeBase, 0};
630                TimeValue sampleTime;
631                const char *str = [sl->line UTF8String];
632                unsigned sampleLen = strlen(str);
633                Handle sampleHndl;
634               
635                PtrToHand(str, &sampleHndl, sampleLen);
636                err = AddMediaSample(theMedia, sampleHndl, 0, sampleLen, sl->end_time - sl->begin_time, (SampleDescriptionHandle)textDesc, 1, 0, &sampleTime);
637               
638                if (err) {
639                        err = GetMoviesError();
640                        Codecprintf(stderr,"error %d adding line from %d to %d in external subtitles",err,sl->begin_time,sl->end_time);
641                } else {
642                        ConvertTimeScale(&startTime, movieTimeScale);
643                        InsertMediaIntoTrack(theTrack, startTime.value.lo, sampleTime, sl->end_time - sl->begin_time, movieRate);
644                }
645               
646                err = noErr;
647                DisposeHandle(sampleHndl);
648        }
649       
650        EndMediaEdits(theMedia);
651       
652        if (*firstSubTrack == NULL)
653                *firstSubTrack = theTrack;
654        else
655                SetTrackAlternate(*firstSubTrack, theTrack);
656       
657        SetMediaLanguage(theMedia, GetFilenameLanguage(filename));
658       
659bail:
660        [ss release];
661        [pool release];
662       
663        if (err) {
664                if (theMedia)
665                        DisposeTrackMedia(theMedia);
666               
667                if (theTrack)
668                        DisposeMovieTrack(theTrack);
669        }
670       
671        if (textDesc)    DisposeHandle((Handle)textDesc);
672        if (header)      DisposeHandle((Handle)header);
673        if (dataRefHndl) DisposeHandle((Handle)dataRefHndl);
674       
675        return err;
676}
677
678#pragma mark IDX Parsing
679
680static NSString *getNextVobSubLine(NSEnumerator *lineEnum)
681{
682        NSString *line;
683        while ((line = [lineEnum nextObject]) != nil) {
684                //Reject empty lines which may contain whitespace
685                if([line length] < 3)
686                        continue;
687               
688                if([line characterAtIndex:0] == '#')
689                        continue;
690               
691                break;
692        }
693        return line;
694}
695
696static Media createVobSubMedia(Movie theMovie, Rect movieBox, ImageDescriptionHandle *imgDescHand, Handle dataRef, OSType dataRefType, VobSubTrack *track)
697{
698        ImageDescriptionHandle imgDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription));
699        *imgDescHand = imgDesc;
700        (*imgDesc)->idSize = sizeof(ImageDescription);
701        (*imgDesc)->cType = kSubFormatVobSub;
702        (*imgDesc)->frameCount = 1;
703        (*imgDesc)->depth = 32;
704        (*imgDesc)->clutID = -1;
705        Fixed trackWidth = IntToFixed(movieBox.right - movieBox.left);
706        Fixed trackHeight = IntToFixed(movieBox.bottom - movieBox.top);
707        (*imgDesc)->width = FixedToInt(trackWidth);
708        (*imgDesc)->height = FixedToInt(trackHeight);
709        Track theTrack = NewMovieTrack(theMovie, trackWidth, trackHeight, kNoVolume);
710        if(theTrack == NULL)
711                return NULL;
712       
713        Media theMedia = NewTrackMedia(theTrack, VideoMediaType, 1000, dataRef, dataRefType);
714        if(theMedia == NULL)
715        {
716                DisposeMovieTrack(theTrack);
717                return NULL;
718        }
719        MediaHandler mh = GetMediaHandler(theMedia);
720        MediaSetGraphicsMode(mh, graphicsModePreBlackAlpha, NULL);
721        SetTrackLayer(theTrack, -1);
722       
723        Handle imgDescExt = NewHandle([track->privateData length]);
724        memcpy(*imgDescExt, [track->privateData bytes], [track->privateData length]);
725       
726        AddImageDescriptionExtension(imgDesc, imgDescExt, kVobSubIdxExtension);
727        DisposeHandle(imgDescExt);
728       
729        return theMedia;
730}
731
732static Boolean ReadPacketTimes(uint8_t *packet, uint32_t length, uint16_t *startTime, uint16_t *endTime, uint8_t *forced) {
733        // to set whether the key sequences 0x01 - 0x02 have been seen
734        Boolean loop = TRUE;
735        *startTime = *endTime = 0;
736        *forced = 0;
737
738        int controlOffset = (packet[2] << 8) + packet[3];
739        while(loop)
740        {       
741                if(controlOffset > length)
742                        return NO;
743                uint8_t *controlSeq = packet + controlOffset;
744                int32_t i = 4;
745                int32_t end = length - controlOffset;
746                uint16_t timestamp = (controlSeq[0] << 8) | controlSeq[1];
747                uint16_t nextOffset = (controlSeq[2] << 8) + controlSeq[3];
748                while (i < end) {
749                        switch (controlSeq[i]) {
750                                case 0x00:
751                                        *forced = 1;
752                                        i++;
753                                        break;
754                                       
755                                case 0x01:
756                                        *startTime = (timestamp << 10) / 90;
757                                        i++;
758                                        break;
759                               
760                                case 0x02:
761                                        *endTime = (timestamp << 10) / 90;
762                                        i++;
763                                        loop = false;
764                                        break;
765                                       
766                                case 0x03:
767                                        // palette info, we don't care
768                                        i += 3;
769                                        break;
770                                       
771                                case 0x04:
772                                        // alpha info, we don't care
773                                        i += 3;
774                                        break;
775                                       
776                                case 0x05:
777                                        // coordinates of image, ffmpeg takes care of this
778                                        i += 7;
779                                        break;
780                                       
781                                case 0x06:
782                                        // offset of the first graphic line, and second, ffmpeg takes care of this
783                                        i += 5;
784                                        break;
785                                       
786                                case 0xff:
787                                        // end of control sequence
788                                        if(controlOffset == nextOffset)
789                                                loop = false;
790                                        controlOffset = nextOffset;
791                                        i = INT_MAX;
792                                        break;
793                                       
794                                default:
795                                        Codecprintf(NULL, " !! Unknown control sequence 0x%02x  aborting (offset %x)\n", controlSeq[i], i);
796                                        return NO;
797                                        break;
798                        }
799                }
800                if(i != INT_MAX)
801                {
802                        //End of packet
803                        loop = false;
804                }
805        }
806        return YES;
807}
808
809typedef struct {
810        Movie theMovie;
811        OSType dataRefType;
812        Handle dataRef;
813        int imageWidth;
814        int imageHeight;
815        Rect movieBox;
816        NSData *subFileData;
817} VobSubInfo;
818
819static OSErr loadTrackIntoMovie(VobSubTrack *track, VobSubInfo info, uint8_t onlyForced, Track *theTrack, uint8_t *hasForcedSubtitles)
820{
821        ImageDescriptionHandle imgDesc;
822        Media trackMedia = createVobSubMedia(info.theMovie, info.movieBox, &imgDesc, info.dataRef, info.dataRefType, track);
823        if(info.imageWidth != 0)
824        {
825                (*imgDesc)->width = info.imageWidth;
826                (*imgDesc)->height = info.imageHeight;
827        }
828       
829        int sampleCount = [track->samples count];
830        int totalSamples = 0;
831        SampleReference64Ptr samples = (SampleReference64Ptr)calloc(sampleCount*2, sizeof(SampleReference64Record));
832        SampleReference64Ptr sample = samples;
833        int i;
834        uint32_t lastTime = 0;
835        VobSubSample *firstSample = nil;
836        for(i=0; i<sampleCount; i++)
837        {
838                VobSubSample *currentSample = [track->samples objectAtIndex:i];
839                int offset = currentSample->fileOffset;
840                int nextOffset;
841                if(i == sampleCount - 1)
842                        nextOffset = [info.subFileData length];
843                else
844                        nextOffset = ((VobSubSample *)[track->samples objectAtIndex:i+1])->fileOffset;
845                int size = nextOffset - offset;
846                if(size < 0)
847                        //Skip samples for which we cannot determine size
848                        continue;
849               
850                NSData *subData = [info.subFileData subdataWithRange:NSMakeRange(offset, size)];
851                uint8_t *extracted = (uint8_t *)malloc(size);
852                int extractedSize = ExtractVobSubPacket(extracted, (const UInt8 *)[subData bytes], size, &size);
853               
854                uint16_t startTimestamp, endTimestamp;
855                uint8_t forced;
856                if(!ReadPacketTimes(extracted, extractedSize, &startTimestamp, &endTimestamp, &forced))
857                        continue;
858                if(onlyForced && !forced)
859                        continue;
860                if(forced)
861                        *hasForcedSubtitles = forced;
862                free(extracted);
863                uint32_t startTime = currentSample->timeStamp + startTimestamp;
864                uint32_t endTime = currentSample->timeStamp + endTimestamp;
865                int duration = endTimestamp - startTimestamp;
866                if(duration <= 0)
867                        //Skip samples which are broken
868                        continue;
869                if(firstSample == nil)
870                {
871                        currentSample->timeStamp = startTime;
872                        firstSample = currentSample;
873                }
874                else if(lastTime != startTime)
875                {
876                        //insert a sample with no real data, to clear the subs
877                        memset(sample, 0, sizeof(SampleReference64Record));
878                        sample->durationPerSample = startTime - lastTime;
879                        sample->numberOfSamples = 1;
880                        sample->dataSize = 1;
881                        totalSamples++;
882                        sample++;
883                }
884               
885                sample->dataOffset.hi = 0;
886                sample->dataOffset.lo = offset;
887                sample->dataSize = size;
888                sample->sampleFlags = 0;
889                sample->durationPerSample = duration;
890                sample->numberOfSamples = 1;
891                lastTime = endTime;
892                totalSamples++;
893                sample++;
894        }
895        AddMediaSampleReferences64(trackMedia, (SampleDescriptionHandle)imgDesc, totalSamples, samples, NULL);
896        free(samples);
897        NSString *langStr = track->language;
898        int lang = langUnspecified;
899        if([langStr length] == 3)
900        {
901                char langCStr[4] = "";
902               
903                CFStringGetCString((CFStringRef)langStr, langCStr, 4, kCFStringEncodingASCII);
904                lang = ISO639_2ToQTLangCode(langCStr);
905        }
906        else if([langStr length] == 2)
907        {
908                char langCStr[3] = "";
909               
910                CFStringGetCString((CFStringRef)langStr, langCStr, 3, kCFStringEncodingASCII);
911                lang = ISO639_1ToQTLangCode(langCStr);                         
912        }
913        SetMediaLanguage(trackMedia, lang);
914       
915        TimeValue mediaDuration = GetMediaDuration(trackMedia);
916        TimeValue movieTimeScale = GetMovieTimeScale(info.theMovie);
917        *theTrack = GetMediaTrack(trackMedia);
918        if(firstSample == nil)
919                firstSample = [track->samples objectAtIndex:0];
920        return InsertMediaIntoTrack(*theTrack, (firstSample->timeStamp * movieTimeScale)/1000, 0, mediaDuration, fixed1);
921}
922
923typedef enum {
924        VOB_SUB_STATE_READING_PRIVATE,
925        VOB_SUB_STATE_READING_TRACK_HEADER,
926        VOB_SUB_STATE_READING_DELAY,
927        VOB_SUB_STATE_READING_TRACK_DATA
928} VobSubState;
929
930static ComponentResult LoadVobSubSubtitles(const FSRef *theDirectory, CFStringRef filename, Movie theMovie, Track *firstSubTrack)
931{
932        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
933        UInt8 path[PATH_MAX];
934       
935        FSRefMakePath(theDirectory, path, PATH_MAX);
936        NSString *nsPath = [[NSString stringWithUTF8String:(char *)path] stringByAppendingPathComponent:(NSString *)filename];
937        NSString *idxContent = [NSString stringWithContentsOfFile:nsPath];
938        NSData *privateData = nil;
939        ComponentResult err = noErr;
940       
941        VobSubState state = VOB_SUB_STATE_READING_PRIVATE;
942        VobSubTrack *currentTrack = nil;
943        int imageWidth = 0, imageHeight = 0;
944        long delay=0;
945
946        NSString *subFileName = [[nsPath stringByDeletingPathExtension] stringByAppendingPathExtension:@"sub"];
947
948        if([[NSFileManager defaultManager] fileExistsAtPath:subFileName] && [idxContent length]) {
949        int subFileSize = [[[[NSFileManager defaultManager] fileAttributesAtPath:subFileName traverseLink:NO] objectForKey:NSFileSize] intValue];
950       
951        NSArray *lines = [idxContent componentsSeparatedByString:@"\n"];
952        NSMutableArray *privateLines = [NSMutableArray array];
953        NSEnumerator *lineEnum = [lines objectEnumerator];
954        NSString *line;
955        Rect movieBox;
956        GetMovieBox(theMovie, &movieBox);
957       
958        NSMutableArray *tracks = [NSMutableArray array];
959       
960        while((line = getNextVobSubLine(lineEnum)) != NULL)
961        {
962                if([line hasPrefix:@"timestamp: "])
963                        state = VOB_SUB_STATE_READING_TRACK_DATA;
964                else if([line hasPrefix:@"id: "])
965                {
966                        if(privateData == nil)
967                        {
968                                NSString *allLines = [privateLines componentsJoinedByString:@"\n"];
969                                privateData = [allLines dataUsingEncoding:NSUTF8StringEncoding];
970                        }
971                        state = VOB_SUB_STATE_READING_TRACK_HEADER;
972                }
973                else if([line hasPrefix:@"delay: "])
974                        state = VOB_SUB_STATE_READING_DELAY;
975                else if(state != VOB_SUB_STATE_READING_PRIVATE)
976                        state = VOB_SUB_STATE_READING_TRACK_HEADER;
977               
978                switch(state)
979                {
980                        case VOB_SUB_STATE_READING_PRIVATE:
981                                [privateLines addObject:line];
982                                if([line hasPrefix:@"size: "])
983                                {
984                                        sscanf([line UTF8String], "size: %dx%d", &imageWidth, &imageHeight);
985                                }
986                                break;
987                        case VOB_SUB_STATE_READING_TRACK_HEADER:
988                                if([line hasPrefix:@"id: "])
989                                {
990                                        char *langStr = (char *)malloc([line length]);
991                                        int index;
992                                        sscanf([line UTF8String], "id: %s index: %d", langStr, &index);
993                                        int langLength = strlen(langStr);
994                                        if(langLength > 0 && langStr[langLength-1] == ',')
995                                                langStr[langLength-1] = 0;
996                                        NSString *language = [NSString stringWithUTF8String:langStr];
997                                       
998                                        currentTrack = [[VobSubTrack alloc] initWithPrivateData:privateData language:language andIndex:index];
999                                        [tracks addObject:currentTrack];
1000                                        [currentTrack release];
1001                                }
1002                                break;
1003                        case VOB_SUB_STATE_READING_DELAY:
1004                                delay = ParseSubTime([[line substringFromIndex:7] UTF8String], 1000, YES);
1005                                break;
1006                        case VOB_SUB_STATE_READING_TRACK_DATA:
1007                        {
1008                                char *timeStr = (char *)malloc([line length]);
1009                                unsigned int position;
1010                                sscanf([line UTF8String], "timestamp: %s filepos: %x", timeStr, &position);
1011                                long time = ParseSubTime(timeStr, 1000, YES);
1012                                free(timeStr);
1013                                if(position > subFileSize)
1014                                        position = subFileSize;
1015                                [currentTrack addSampleTime:time + delay offset:position];
1016                        }
1017                                break;
1018                }
1019        }
1020               
1021        if([tracks count])
1022        {
1023                OSType dataRefType;
1024                Handle dataRef = NULL;
1025               
1026                NSData *subFileData = [NSData dataWithContentsOfMappedFile:subFileName];
1027                FSRef subFile;
1028                FSPathMakeRef((const UInt8 *)[subFileName fileSystemRepresentation], &subFile, NULL);
1029                FSSpec theFile;
1030                FSGetCatalogInfo(&subFile, kFSCatInfoNone, NULL, NULL, &theFile, NULL);
1031               
1032                if((err = QTNewDataReferenceFromFSSpec(&theFile, 0, &dataRef, &dataRefType)) != noErr)
1033                        goto bail;
1034               
1035                NSEnumerator *trackEnum = [tracks objectEnumerator];
1036                VobSubTrack *track = nil;
1037                while((track = [trackEnum nextObject]) != nil)
1038                {
1039                        Track theTrack;
1040                        VobSubInfo info = {theMovie, dataRefType, dataRef, imageWidth, imageHeight, movieBox, subFileData};
1041                        uint8_t hasForced = 0;
1042                        err = loadTrackIntoMovie(track, info, 0, &theTrack, &hasForced);
1043                        if(hasForced)
1044                        {
1045                                Track forcedTrack;
1046                                err = loadTrackIntoMovie(track, info, 1, &forcedTrack, &hasForced);
1047                                if(*firstSubTrack == NULL)
1048                                        *firstSubTrack = forcedTrack;
1049                                else
1050                                        SetTrackAlternate(*firstSubTrack, forcedTrack);
1051                        }
1052                       
1053                        if (*firstSubTrack == NULL)
1054                                *firstSubTrack = theTrack;
1055                        else
1056                                SetTrackAlternate(*firstSubTrack, theTrack);
1057                }
1058        }
1059        }
1060bail:
1061        [pool release];
1062       
1063        return err;
1064}
1065
1066static Boolean ShouldLoadExternalSubtitles()
1067{
1068        Boolean isSet, value;
1069       
1070        value = CFPreferencesGetAppBooleanValue(CFSTR("LoadExternalSubtitles"),CFSTR("org.perian.Perian"),&isSet);
1071       
1072        return isSet ? value : YES;
1073}
1074
1075ComponentResult LoadExternalSubtitles(const FSRef *theFile, Movie theMovie)
1076{
1077        ComponentResult err = noErr;
1078        Track firstSubTrack = NULL;
1079        HFSUniStr255 hfsFilename;
1080        CFStringRef cfFilename = NULL;
1081        FSRef parentDir;
1082        FSIterator dirItr = NULL;
1083        CFRange baseFilenameRange;
1084        ItemCount filesFound;
1085        Boolean containerChanged;
1086       
1087        if (!ShouldLoadExternalSubtitles()) return noErr;
1088       
1089        err = FSGetCatalogInfo(theFile, kFSCatInfoNone, NULL, &hfsFilename, NULL, &parentDir);
1090        if (err) goto bail;
1091       
1092        // find the location of the extension
1093        cfFilename = CFStringCreateWithCharacters(NULL, hfsFilename.unicode, hfsFilename.length);
1094        baseFilenameRange = CFStringFind(cfFilename, CFSTR("."), kCFCompareBackwards);
1095       
1096        // strip the extension
1097        if (baseFilenameRange.location != kCFNotFound) {
1098                CFStringRef temp = cfFilename;
1099                baseFilenameRange.length = baseFilenameRange.location;
1100                baseFilenameRange.location = 0;
1101                cfFilename = CFStringCreateWithSubstring(NULL, temp, baseFilenameRange);
1102                CFRelease(temp);
1103        }
1104       
1105        err = FSOpenIterator(&parentDir, kFSIterateFlat, &dirItr);
1106        if (err) goto bail;
1107       
1108        do {
1109                FSRef foundFileRef;
1110                HFSUniStr255 hfsFoundFilename;
1111                CFStringRef cfFoundFilename = NULL;
1112                CFComparisonResult cmpRes;
1113               
1114                err = FSGetCatalogInfoBulk(dirItr, 1, &filesFound, &containerChanged, kFSCatInfoNone,
1115                                                   NULL, &foundFileRef, NULL, &hfsFoundFilename);
1116                if (err) goto bail;
1117               
1118                cfFoundFilename = CFStringCreateWithCharacters(NULL, hfsFoundFilename.unicode, hfsFoundFilename.length);
1119                cmpRes = CFStringCompareWithOptions(cfFoundFilename, cfFilename, baseFilenameRange, kCFCompareCaseInsensitive);
1120               
1121                // two files share the same base, so now check the extension of the found file
1122                if (cmpRes == kCFCompareEqualTo) {
1123                        CFRange extRange = { CFStringGetLength(cfFoundFilename) - 4, 4 };
1124                        CFRange actRange;
1125                        int subType = -1;
1126                       
1127                        // SubRip
1128                        actRange = CFStringFind(cfFoundFilename, CFSTR(".srt"), kCFCompareCaseInsensitive | kCFCompareBackwards);
1129                        if (actRange.length && actRange.location == extRange.location)
1130                                subType = kSubTypeSRT;
1131                        else {
1132                                // SubStationAlpha
1133                                actRange = CFStringFind(cfFoundFilename, CFSTR(".ass"), kCFCompareCaseInsensitive | kCFCompareBackwards);
1134                                if (actRange.length && actRange.location == extRange.location)
1135                                        subType = kSubTypeASS;
1136                                else {
1137                                        actRange = CFStringFind(cfFoundFilename, CFSTR(".ssa"), kCFCompareCaseInsensitive | kCFCompareBackwards);
1138                                        if (actRange.length && actRange.location == extRange.location)
1139                                                subType = kSubTypeSSA;
1140                                        else {
1141                                                // VobSub
1142                                                actRange = CFStringFind(cfFoundFilename, CFSTR(".idx"), kCFCompareCaseInsensitive | kCFCompareBackwards);
1143                                                if (actRange.length && actRange.location == extRange.location)
1144                                                        err = LoadVobSubSubtitles(&parentDir, cfFoundFilename, theMovie, &firstSubTrack);
1145                                                else {
1146                                                        actRange = CFStringFind(cfFoundFilename, CFSTR(".smi"), kCFCompareCaseInsensitive | kCFCompareBackwards);
1147                                                        if (actRange.length && actRange.location == extRange.location)
1148                                                                subType = kSubTypeSMI;
1149                                                }
1150                                        }
1151                                }
1152                        }
1153                       
1154                        if (subType == kSubTypeSMI) {
1155                                err = LoadSingleTextSubtitle(&parentDir, cfFoundFilename, theMovie, &firstSubTrack, kSubTypeSMI, 1);
1156                                if (!err) err = LoadSingleTextSubtitle(&parentDir, cfFoundFilename, theMovie, &firstSubTrack, kSubTypeSMI, 2);
1157                        }
1158                        else if (subType != -1)
1159                                err = LoadSingleTextSubtitle(&parentDir, cfFoundFilename, theMovie, &firstSubTrack, subType, 0);
1160
1161                        if (err) goto bail;
1162                }
1163               
1164                CFRelease(cfFoundFilename);
1165        } while (filesFound && !containerChanged);
1166       
1167bail:   
1168                if (err == errFSNoMoreItems)
1169                        err = noErr;
1170       
1171        if (dirItr)
1172                FSCloseIterator(dirItr);
1173       
1174        if (cfFilename)
1175                CFRelease(cfFilename);
1176       
1177        return err;
1178}
1179
1180ComponentResult LoadExternalSubtitlesFromFileDataRef(Handle dataRef, OSType dataRefType, Movie theMovie)
1181{
1182        if (dataRefType != AliasDataHandlerSubType) return noErr;
1183       
1184        CFStringRef cfPath;
1185        FSRef ref;
1186        uint8_t path[PATH_MAX];
1187       
1188        QTGetDataReferenceFullPathCFString(dataRef, dataRefType, kQTPOSIXPathStyle, &cfPath);
1189        CFStringGetCString(cfPath, (char*)path, PATH_MAX, kCFStringEncodingUTF8);
1190        FSPathMakeRef(path, &ref, NULL);
1191        CFRelease(cfPath);
1192       
1193        return LoadExternalSubtitles(&ref, theMovie);
1194}
1195
1196#pragma mark Obj-C Classes
1197
1198@implementation SubSerializer
1199-(id)init
1200{
1201        if (self = [super init]) {
1202                lines = [[NSMutableArray alloc] init];
1203                finished = NO;
1204                last_begin_time = last_end_time = 0;
1205                linesInput = 0;
1206        }
1207       
1208        return self;
1209}
1210
1211-(void)dealloc
1212{
1213        [lines release];
1214        [super dealloc];
1215}
1216
1217static CFComparisonResult CompareLinesByBeginTime(const void *a, const void *b, void *unused)
1218{
1219        SubLine *al = (SubLine*)a, *bl = (SubLine*)b;
1220       
1221        if (al->begin_time > bl->begin_time) return kCFCompareGreaterThan;
1222        if (al->begin_time < bl->begin_time) return kCFCompareLessThan;
1223       
1224        if (al->no > bl->no) return kCFCompareGreaterThan;
1225        if (al->no < bl->no) return kCFCompareLessThan;
1226        return kCFCompareEqualTo;
1227}
1228
1229static int cmp_uint(const void *a, const void *b)
1230{
1231        unsigned av = *(unsigned*)a, bv = *(unsigned*)b;
1232       
1233        if (av > bv) return 1;
1234        if (av < bv) return -1;
1235        return 0;
1236}
1237
1238-(void)addLine:(SubLine *)line
1239{
1240        if (line->begin_time >= line->end_time) {
1241                if (line->begin_time)
1242                        Codecprintf(NULL, "Invalid times (%d and %d) for line \"%s\"", line->begin_time, line->end_time, [line->line UTF8String]);
1243                return;
1244        }
1245       
1246        line->no = linesInput++;
1247       
1248        int nlines = [lines count];
1249       
1250        if (!nlines || line->begin_time > ((SubLine*)[lines objectAtIndex:nlines-1])->begin_time) {
1251                [lines addObject:line];
1252        } else {
1253                CFIndex i = CFArrayBSearchValues((CFArrayRef)lines, CFRangeMake(0, nlines), line, CompareLinesByBeginTime, NULL);
1254               
1255                if (i >= nlines)
1256                        [lines addObject:line];
1257                else
1258                        [lines insertObject:line atIndex:i];
1259        }
1260       
1261}
1262
1263-(SubLine*)getNextRealSerializedPacket
1264{
1265        int nlines = [lines count];
1266        SubLine *first = [lines objectAtIndex:0];
1267        int i;
1268
1269        if (!finished) {
1270                if (nlines > 1) {
1271                        unsigned maxEndTime = first->end_time;
1272                       
1273                        for (i = 1; i < nlines; i++) {
1274                                SubLine *l = [lines objectAtIndex:i];
1275                               
1276                                if (l->begin_time >= maxEndTime) {
1277                                        goto canOutput;
1278                                }
1279                               
1280                                maxEndTime = MAX(maxEndTime, l->end_time);
1281                        }
1282                }
1283               
1284                return nil;
1285        }
1286       
1287canOutput:
1288        NSMutableString *str = [NSMutableString stringWithString:first->line];
1289        unsigned begin_time = last_end_time, end_time = first->end_time;
1290        int deleted = 0;
1291               
1292        for (i = 1; i < nlines; i++) {
1293                SubLine *l = [lines objectAtIndex:i];
1294                if (l->begin_time >= end_time) break;
1295               
1296                //shorten packet end time if another shorter time (begin or end) is found
1297                //as long as it isn't the begin time
1298                end_time = MIN(end_time, l->end_time);
1299                if (l->begin_time > begin_time)
1300                        end_time = MIN(end_time, l->begin_time);
1301               
1302                if (l->begin_time <= begin_time)
1303                        [str appendString:l->line];
1304        }
1305       
1306        for (i = 0; i < nlines; i++) {
1307                SubLine *l = [lines objectAtIndex:i - deleted];
1308               
1309                if (l->end_time == end_time) {
1310                        [lines removeObjectAtIndex:i - deleted];
1311                        deleted++;
1312                }
1313        }
1314       
1315        return [[SubLine alloc] initWithLine:str start:begin_time end:end_time];
1316}
1317
1318-(SubLine*)getSerializedPacket
1319{
1320        int nlines = [lines count];
1321
1322        if (!nlines) return nil;
1323       
1324        SubLine *nextline = [lines objectAtIndex:0], *ret;
1325       
1326        if (nextline->begin_time > last_end_time) {
1327                ret = [[SubLine alloc] initWithLine:@"\n" start:last_end_time end:nextline->begin_time];
1328        } else {
1329                ret = [self getNextRealSerializedPacket];
1330        }
1331       
1332        if (!ret) return nil;
1333       
1334        last_begin_time = ret->begin_time;
1335        last_end_time   = ret->end_time;
1336               
1337        return [ret autorelease];
1338}
1339
1340-(void)setFinished:(BOOL)_finished
1341{
1342        finished = _finished;
1343}
1344
1345-(BOOL)isEmpty
1346{
1347        return [lines count] == 0;
1348}
1349
1350-(NSString*)description
1351{
1352        return [NSString stringWithFormat:@"lines left: %d finished inputting: %d",[lines count],finished];
1353}
1354@end
1355
1356@implementation SubLine
1357-(id)initWithLine:(NSString*)l start:(unsigned)s end:(unsigned)e
1358{
1359        if (self = [super init]) {
1360                if ([l characterAtIndex:[l length]-1] != '\n') l = [l stringByAppendingString:@"\n"];
1361                line = [l retain];
1362                begin_time = s;
1363                end_time = e;
1364                no = 0;
1365        }
1366       
1367        return self;
1368}
1369
1370-(void)dealloc
1371{
1372        [line release];
1373        [super dealloc];
1374}
1375
1376-(NSString*)description
1377{
1378        return [NSString stringWithFormat:@"\"%@\", from %d s to %d s",[line substringToIndex:[line length]-1],begin_time,end_time];
1379}
1380@end
1381
1382@implementation VobSubSample
1383
1384- (id)initWithTime:(long)time offset:(long)offset
1385{
1386        self = [super init];
1387        if(!self)
1388                return self;
1389       
1390        timeStamp = time;
1391        fileOffset = offset;
1392       
1393        return self;
1394}
1395
1396@end
1397
1398@implementation VobSubTrack
1399
1400- (id)initWithPrivateData:(NSData *)idxPrivateData language:(NSString *)lang andIndex:(int)trackIndex
1401{
1402        self = [super init];
1403        if(!self)
1404                return self;
1405       
1406        privateData = [idxPrivateData retain];
1407        language = [lang retain];
1408        index = trackIndex;
1409        samples = [[NSMutableArray alloc] init];
1410       
1411        return self;
1412}
1413
1414- (void)dealloc
1415{
1416        [privateData release];
1417        [language release];
1418        [samples release];
1419        [super dealloc];
1420}
1421
1422- (void)addSample:(VobSubSample *)sample
1423{
1424        [samples addObject:sample];
1425}
1426
1427- (void)addSampleTime:(long)time offset:(long)offset
1428{
1429        VobSubSample *sample = [[VobSubSample alloc] initWithTime:time offset:offset];
1430        [self addSample:sample];
1431        [sample release];
1432}
1433
1434@end
1435
1436#pragma mark C++ Wrappers
1437
1438CXXSubSerializer::CXXSubSerializer()
1439{
1440        priv = [[SubSerializer alloc] init];
1441    CFRetain(priv);
1442}
1443
1444CXXSubSerializer::~CXXSubSerializer()
1445{
1446        if (priv) {CFRelease(priv); [(SubSerializer*)priv release]; priv = NULL;}
1447}
1448
1449void CXXSubSerializer::pushLine(const char *line, size_t size, unsigned start, unsigned end)
1450{
1451        NSString *str = [[NSString alloc] initWithBytes:line length:size encoding:NSUTF8StringEncoding];
1452        NSString *strn = [str stringByAppendingString:@"\n"];
1453        [str release];
1454       
1455        SubLine *sl = [[SubLine alloc] initWithLine:strn start:start end:end];
1456       
1457        [sl autorelease];
1458       
1459        [(SubSerializer*)priv addLine:sl];
1460}
1461
1462void CXXSubSerializer::setFinished()
1463{
1464        [(SubSerializer*)priv setFinished:YES];
1465}
1466
1467const char *CXXSubSerializer::popPacket(size_t *size, unsigned *start, unsigned *end)
1468{
1469        SubLine *sl = [(SubSerializer*)priv getSerializedPacket];
1470        if (!sl) return NULL;
1471        const char *u = [sl->line UTF8String];
1472        *start = sl->begin_time;
1473        *end   = sl->end_time;
1474       
1475        *size = strlen(u);
1476       
1477        return u;
1478}
1479
1480void CXXSubSerializer::release()
1481{
1482        SubSerializer *s = (SubSerializer*)priv;
1483        int r = [s retainCount];
1484       
1485        if (r == 1) delete this;
1486        else [s release];
1487}
1488
1489void CXXSubSerializer::retain()
1490{
1491        [(SubSerializer*)priv retain];
1492}
1493
1494bool CXXSubSerializer::empty()
1495{
1496        return [(SubSerializer*)priv isEmpty];
1497}
1498
1499CXXAutoreleasePool::CXXAutoreleasePool()
1500{
1501        pool = [[NSAutoreleasePool alloc] init];
1502}
1503
1504CXXAutoreleasePool::~CXXAutoreleasePool()
1505{
1506        [(NSAutoreleasePool*)pool release];
1507}
Note: See TracBrowser for help on using the repository browser.