source: trunk/Subtitles/SubImport.mm @ 1201

Revision 1201, 41.0 KB checked in by astrange, 5 years ago (diff)

Cleanup file path URLs in SubImport?.

  • Use CFURL instead of char[PATH_MAX].
  • Use -UTF8String to make language-code lookups shorter.

(-UTF8String is guaranteed to work; CFString functions aren't.)

Also avoid a copy of each line in MKV import.

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