source: trunk/Subtitles/SubImport.mm @ 1405

Revision 1405, 42.0 KB checked in by astrange, 4 years ago (diff)

Fix scanf format string

%[...] wants a two-byte string, not a char. Possible crash, but I've never
seen it in practice.

Fixes #559.

Patch by Timac2

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