source: trunk/Subtitles/SubImport.mm @ 1036

Revision 1036, 41.4 KB checked in by astrange, 6 years ago (diff)

Build system/logging:

  • Don't regenerate SubParsing?.m/ffmpeg universal libs if not needed
  • Print "Perian" instead of "Perian Codec"

Subtitles:

  • Rewrite SubSerializer?; much shorter and correct now (aside from line order).

(fixes many dropped lines in Live-eviL Captain Harlock and gg Sayonara Zetsubou Sensei)

  • Fix some bugs where line spans were rendered more than once with the wrong style.

(fixes transparency in Spice and Wolf 01 [ADTRW].mkv)

  • Ignore \blur properly

(fixes unstyled lines in [gg]_Goku_Sayonara_Zetsubou_Sensei_-_02_[D284BF24].mkv)

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