Changeset 330

Show
Ignore:
Timestamp:
02/06/07 13:08:02 (2 years ago)
Author:
astrange
Message:

Generalize subtitle overlap handling, apply to external .srt files.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/Perian.xcodeproj/project.pbxproj

    r324 r330  
    226226                61CB120D0ACE0F8D007994BD /* MatroskaCodecIDs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 61D691500AC8E382000EFC7D /* MatroskaCodecIDs.cpp */; }; 
    227227                61CB12260ACE1074007994BD /* MatroskaImport.r in Rez */ = {isa = PBXBuildFile; fileRef = 61D691590AC8E382000EFC7D /* MatroskaImport.r */; }; 
    228                 61D514DE0ADF3DBA00A671E1 /* SubImport.c in Sources */ = {isa = PBXBuildFile; fileRef = 61D514DD0ADF3DBA00A671E1 /* SubImport.c */; }; 
     228                61D514DE0ADF3DBA00A671E1 /* SubImport.mm in Sources */ = {isa = PBXBuildFile; fileRef = 61D514DD0ADF3DBA00A671E1 /* SubImport.mm */; }; 
    229229                61D517730AE0402E00A671E1 /* CommonUtils.c in Sources */ = {isa = PBXBuildFile; fileRef = 61D517720AE0402E00A671E1 /* CommonUtils.c */; }; 
    230230                61FD41330B4F6F0800BEEFEA /* MatroskaImportPrivate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 61FD41320B4F6F0800BEEFEA /* MatroskaImportPrivate.cpp */; }; 
     
    582582                61CB11CB0ACDF50F007994BD /* KaxVersion.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = KaxVersion.cpp; path = libmatroska/src/KaxVersion.cpp; sourceTree = "<group>"; }; 
    583583                61D514DC0ADF3DBA00A671E1 /* SubImport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SubImport.h; sourceTree = "<group>"; }; 
    584                 61D514DD0ADF3DBA00A671E1 /* SubImport.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SubImport.c; sourceTree = "<group>"; }; 
     584                61D514DD0ADF3DBA00A671E1 /* SubImport.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = SubImport.mm; sourceTree = "<group>"; }; 
    585585                61D517710AE0402E00A671E1 /* CommonUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommonUtils.h; sourceTree = "<group>"; }; 
    586586                61D517720AE0402E00A671E1 /* CommonUtils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CommonUtils.c; sourceTree = "<group>"; }; 
     
    880880                                6123D6250AD0A3FF003EDE52 /* VobSubCodecDispatch.h */, 
    881881                                61D514DC0ADF3DBA00A671E1 /* SubImport.h */, 
    882                                 61D514DD0ADF3DBA00A671E1 /* SubImport.c */, 
     882                                61D514DD0ADF3DBA00A671E1 /* SubImport.mm */, 
    883883                                3DB2BB270B6C92F000416863 /* SSARenderCodec.h */, 
    884884                                3DB2BB280B6C92F000416863 /* SSARenderCodec.m */, 
     
    15031503                                6123D6260AD0A3FF003EDE52 /* VobSubCodec.c in Sources */, 
    15041504                                613CD51E0AD1FB650098A825 /* TextSubCodec.c in Sources */, 
    1505                                 61D514DE0ADF3DBA00A671E1 /* SubImport.c in Sources */, 
     1505                                61D514DE0ADF3DBA00A671E1 /* SubImport.mm in Sources */, 
    15061506                                61D517730AE0402E00A671E1 /* CommonUtils.c in Sources */, 
    15071507                                6116E5510B43C27B0020F1CE /* ACBaseCodec.cpp in Sources */, 
  • trunk/SSADocument.h

    r329 r330  
    2020 
    2121#import <Cocoa/Cocoa.h> 
     22#import "SubImport.h" 
    2223 
    2324enum {S_LeftAlign = 0, S_CenterAlign, S_RightAlign}; 
     
    4344} ssastyleline; 
    4445 
    45 @interface SSAEvent : NSObject 
    46 { 
    47         @public 
    48         NSString *line; 
    49         unsigned begin_time, end_time; 
    50 } 
    51 @end 
    52  
    5346@interface SSADocument : NSObject { 
    5447        NSArray *_lines; 
     
    6861} 
    6962-(NSString *)header; 
    70 -(SSAEvent *)movPacket:(int)i; 
     63-(SubLine *)movPacket:(int)i; 
    7164-(void) loadHeader:(NSString*)path width:(float)width height:(float)height; 
    7265-(unsigned)packetCount; 
  • trunk/SSADocument.m

    r329 r330  
    2222#import "Categories.h" 
    2323#include "SubImport.h" 
    24  
    25 @implementation SSAEvent 
    26 -(void)dealloc 
    27 { 
    28         [line release]; 
    29         [super dealloc]; 
    30 } 
    31 @end 
    3224 
    3325@implementation SSADocument 
     
    243235} 
    244236 
    245 -(SSAEvent *)movPacket:(int)i 
     237-(SubLine *)movPacket:(int)i 
    246238{ 
    247239        return [_lines objectAtIndex:i]; 
     
    276268} 
    277269 
    278 static int cmp_uint(const void *a, const void *b) 
    279 { 
    280         unsigned av = *(unsigned*)a, bv = *(unsigned*)b; 
    281          
    282         if (av > bv) return 1; 
    283         if (av < bv) return -1; 
    284         return 0; 
    285 } 
    286  
    287 typedef struct { 
    288         NSString *line; 
    289         unsigned start, end; 
    290 } line_range; 
    291  
    292 static int cmp_line(const void *a, const void *b) 
    293 { 
    294         const line_range *av = a, *bv = b; 
    295          
    296         if (av->start > bv->start) return 1; 
    297         if (av->start < bv->start) return -1; 
    298         return 0; 
    299 } 
    300  
    301270-(NSArray *)serializeSubLines:(NSMutableArray*)linesa 
    302271{ 
    303         int num = [linesa count]; 
    304         unsigned times[num*2]; 
    305         line_range lines[num]; 
    306         unsigned i, j; 
     272        int num = [linesa count], i; 
    307273        NSMutableArray *outa = [[NSMutableArray alloc] init]; 
     274        SubtitleSerializer *serializer = [[SubtitleSerializer alloc] init]; 
     275        SubLine *sl; 
    308276         
    309277        for (i = 0; i < num; i++) { 
    310                 NSDictionary *l
     278                NSDictionary *l = [linesa objectAtIndex:i];
    311279                NSString *s,*e; 
    312                 l = [linesa objectAtIndex:i]; 
    313                 line_range li; 
     280                SubLine *li; 
    314281                 
    315282                s = [l objectForKey:@"Start"]; 
    316283                e = [l objectForKey:@"End"]; 
    317284                 
    318                 li.line = oneMKVPacket(l); 
    319                 li.start = ParseSSATime(s); 
    320                 li.end = ParseSSATime(e); 
     285                li = [[[SubLine alloc] initWithLine:oneMKVPacket(l) start:ParseSSATime(s) end:ParseSSATime(e)] autorelease]; 
    321286                 
    322287                if (timescale != 1.) { 
    323                         li.start *= timescale; 
    324                         li.end *= timescale; 
    325                 } 
    326                  
    327                 times[i*2] = li.start; 
    328                 times[i*2+1] = li.end; 
    329                  
    330                 lines[i] = li; 
    331         } 
    332          
    333         qsort(times, num*2, sizeof(unsigned), cmp_uint); 
    334         qsort(lines, num, sizeof(line_range), cmp_line); 
    335          
    336         for (i = 0; i < num*2; i++) { 
    337                 if (i > 0 && times[i-1] == times[i]) continue; 
    338                 NSMutableString *accum = [NSMutableString string]; 
    339                 unsigned start = times[i], end; 
    340                  
    341                 for (j = 0; j < num; j++) { 
    342                         if (isinrange(times[i], lines[j].start, lines[j].end)) { 
    343                                 end = lines[j].end; 
    344                                 [accum appendString:lines[j].line]; 
    345                         } 
    346                 } 
    347                  
    348                 if ([accum length] > 0) { 
    349                         [accum deleteCharactersInRange:NSMakeRange([accum length] - 1, 1)]; // delete last newline 
    350                         SSAEvent *event = [[[SSAEvent alloc] init] autorelease]; 
    351                          
    352                         event->begin_time = start; 
    353                         event->end_time = end; 
    354                         event->line = [accum retain]; 
    355                          
    356                         [outa addObject:event]; 
    357                 } 
    358         } 
    359          
     288                        li->begin_time *= timescale; 
     289                        li->end_time *= timescale; 
     290                } 
     291 
     292                [serializer addLine:li]; 
     293        } 
     294         
     295        [serializer setFinished:YES]; 
     296         
     297        while (sl = [serializer getSerializedPacket]) { 
     298                [outa addObject:sl]; 
     299        } 
     300         
     301        [serializer release]; 
    360302        return outa; 
    361303} 
     
    600542         
    601543        for (i = 0; i < packetCount; i++) { 
    602                 SSAEvent *p = [ssa movPacket:i]; 
     544                SubLine *p = [ssa movPacket:i]; 
    603545                TimeRecord movieStartTime = {SInt64ToWide(p->begin_time), 100, 0}; 
    604546                TimeValue sampleTime; 
     
    619561        } 
    620562         
     563        sampleHndl = NULL; 
     564         
    621565        EndMediaEdits(theMedia); 
    622566         
     
    645589         
    646590        if (headerHndl) DisposeHandle((Handle)headerHndl); 
     591        if (sampleHndl) DisposeHandle(sampleHndl); 
    647592        DisposeHandle((Handle)drefHndl); 
    648593         
  • trunk/SubImport.h

    r311 r330  
    3030#endif 
    3131 
     32#ifdef __OBJC__ 
     33#import <Cocoa/Cocoa.h> 
     34 
     35@interface SubLine : NSObject 
     36{ 
     37        @public 
     38        NSString *line; 
     39        unsigned begin_time, end_time; 
     40} 
     41-(id)initWithLine:(NSString*)l start:(unsigned)s end:(unsigned)e; 
     42@end 
     43 
     44@interface SubtitleSerializer : NSObject 
     45{ 
     46        NSMutableArray *lines, *outpackets; 
     47        BOOL finished; 
     48} 
     49-(void)addLine:(SubLine *)sline; 
     50-(void)addLineWithString:(NSString*)string start:(unsigned)start end:(unsigned)end; 
     51-(void)setFinished:(BOOL)finished; 
     52-(SubLine*)getSerializedPacket; 
     53@end 
    3254#endif 
     55 
     56#endif 
  • trunk/SubImport.mm

    r313 r330  
    99 
    1010#include <QuickTime/QuickTime.h> 
    11 #include "SubImport.h" 
     11#import "SubImport.h" 
     12#import "Categories.h" 
    1213#include "CommonUtils.h" 
    1314 
     
    9293} 
    9394 
    94 extern ComponentResult LoadSubStationAlphaSubtitles(const FSRef *theDirectory, CFStringRef filename, Movie theMovie, Track *firstSubTrack); 
     95extern "C" ComponentResult LoadSubStationAlphaSubtitles(const FSRef *theDirectory, CFStringRef filename, Movie theMovie, Track *firstSubTrack); 
    9596 
    9697ComponentResult LoadSubRipSubtitles(const FSRef *theDirectory, CFStringRef filename, Movie theMovie, Track *firstSubTrack) 
    9798{ 
     99        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
     100        SubtitleSerializer *serializer = [[SubtitleSerializer alloc] init]; 
    98101        ComponentResult err = noErr; 
    99102        Handle dataRef = NULL; 
    100         OSType dataRefType = rAliasType; 
    101         HFSUniStr255 hfsFilename; 
    102         CFRange filenameLen; 
    103103        Track theTrack = NULL; 
    104104        Media theMedia = NULL; 
    105105        Rect movieBox; 
    106  
    107         ComponentInstance dataHandler = NULL
    108         long filePos = 0
    109         long fileSize; 
    110         char *data = NULL; 
     106        SubLine *sl; 
     107        TimeScale movieTimeScale = GetMovieTimeScale(theMovie)
     108        UInt32 emptyDataRefExtension[2]
     109 
     110        const char *data = NULL; 
    111111        ImageDescriptionHandle textDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription)); 
    112  
    113         filenameLen.location = 0;        
    114         filenameLen.length = hfsFilename.length = CFStringGetLength(filename); 
    115         CFStringGetCharacters(filename, filenameLen, hfsFilename.unicode); 
    116  
    117         err = FSNewAliasMinimalUnicode(theDirectory, hfsFilename.length, hfsFilename.unicode, (AliasHandle *)&dataRef, NULL); 
    118         if (err) goto bail; 
    119  
    120         err = OpenAComponent(GetDataHandler(dataRef, dataRefType, kDataHCanRead), &dataHandler); 
    121         if (err) goto bail; 
    122  
    123         err = DataHSetDataRef(dataHandler, dataRef); 
    124         if (err) goto bail; 
    125  
    126         err = DataHOpenForRead(dataHandler); 
    127         if (err) goto bail; 
    128  
    129         err = DataHGetFileSize(dataHandler, &fileSize); 
    130         if (err) goto bail; 
    131  
    132         // cap subs at 5 megabytes; any more is really insane 
    133         if (fileSize > 5*1024*1024) 
    134                 goto bail; 
    135  
    136         // load the subtitle file 
    137         data = NewPtr(fileSize + 1); 
    138         err = DataHScheduleData(dataHandler, data, 0, fileSize, 0, NULL, NULL); 
    139         if (err) goto bail; 
    140         data[fileSize] = '\0'; 
    141  
     112        unsigned int subNum = 1; 
     113        UInt8 *path = (UInt8*)malloc(PATH_MAX); 
     114        NSString *srtfile; 
     115        NSError *nserr = nil; 
     116        Handle sampleHndl = NULL; 
     117         
     118        FSRefMakePath(theDirectory, path, PATH_MAX); 
     119        srtfile = [[NSString stringWithUTF8String:(char*)path] stringByAppendingPathComponent:(NSString*)filename]; 
     120        data = [[[NSString stringWithContentsOfFile:srtfile encoding:NSUTF8StringEncoding error:&nserr] stringByStandardizingNewlines] UTF8String]; 
     121        free(path); 
     122         
     123        dataRef = NewHandleClear(sizeof(Handle) + 1); 
     124        emptyDataRefExtension[0] = EndianU32_NtoB(sizeof(UInt32)*2); 
     125        emptyDataRefExtension[1] = EndianU32_NtoB(kDataRefExtensionInitializationData); 
     126         
     127        PtrAndHand(&emptyDataRefExtension[0], dataRef, sizeof(emptyDataRefExtension)); 
     128         
    142129        GetMovieBox(theMovie, &movieBox); 
    143130 
    144         // millisecond accuracy 
    145         theTrack = CreatePlaintextSubTrack(theMovie, textDesc, 1000, dataRef, dataRefType, kSubFormatUTF8, NULL, movieBox); 
     131        theTrack = CreatePlaintextSubTrack(theMovie, textDesc, 1000, dataRef, HandleDataHandlerSubType, kSubFormatUTF8, NULL, movieBox); 
    146132        if (theTrack == NULL) { 
    147133                err = GetMoviesError(); 
     
    155141        } 
    156142 
    157         unsigned int subNum = 1; 
    158143        char subNumStr[10]; 
    159144 
    160         sprintf(subNumStr, "%u", subNum); 
     145        snprintf(subNumStr, 10, "%u", subNum); 
    161146        char *subOffset = strstr(data, subNumStr); 
    162147 
     
    177162                // find the next subtitle 
    178163                // setting subOffset to point the end of the current subtitle text 
    179                 sprintf(subNumStr, "\n%u", ++subNum); 
     164                snprintf(subNumStr, 10, "\n%u", ++subNum); 
    180165                subOffset = strstr(subTimecodeOffset, subNumStr); 
    181166 
     
    185170 
    186171                        TimeValue startTime = startmsec + 1000*startsec + 60*1000*startmin + 60*60*1000*starthour; 
    187                         TimeValue duration = endmsec + 1000*endsec + 60*1000*endmin + 60*60*1000*endhour - startTime; 
    188                         TimeValue sampleTime; 
    189                         TimeRecord movieStartTime = { SInt64ToWide(startTime), 1000, 0 }; 
     172                        TimeValue endTime = endmsec + 1000*endsec + 60*1000*endmin + 60*60*1000*endhour; 
    190173 
    191174                        if (subOffset != NULL) { 
    192                                 if(subOffset - subTextOffset - 1 > 0 && duration > 0) //Make sure subtitle is not empty 
     175                                if(subOffset - subTextOffset - 1 > 0 && endTime > startTime) //Make sure subtitle is not empty 
    193176                                { 
    194                                         err = AddMediaSampleReference(theMedia, subTextOffset - data,  
    195                                                                                                   subOffset - subTextOffset - 1,  
    196                                                                                                   duration, (SampleDescriptionHandle) textDesc,  
    197                                                                                                   1, 0, &sampleTime); 
    198                                         if (err) goto bail; 
    199  
    200                                         ConvertTimeScale(&movieStartTime, GetMovieTimeScale(theMovie)); 
    201  
    202                                         err = InsertMediaIntoTrack(theTrack, movieStartTime.value.lo, sampleTime, duration, fixed1); 
    203  
    204                                         if (err) goto bail; 
     177                                        NSString *l = [[NSString alloc] initWithBytes:subTextOffset length:subOffset - subTextOffset encoding:NSUTF8StringEncoding]; 
     178                                        sl = [[SubLine alloc] initWithLine:l start:startTime end:endTime]; 
     179                                         
     180                                        [l autorelease]; 
     181                                        [sl autorelease]; 
     182 
     183                                        [serializer addLine:sl]; 
    205184                                } 
    206185 
     
    211190                } 
    212191        } 
     192         
     193        [serializer setFinished:YES]; 
     194         
     195        BeginMediaEdits(theMedia); 
     196         
     197        while (sl = [serializer getSerializedPacket]) { 
     198                TimeRecord movieStartTime = {SInt64ToWide(sl->begin_time), 1000, 0}; 
     199                TimeValue sampleTime; 
     200                const char *str = [sl->line UTF8String]; 
     201                size_t sampleLen = strlen(str); 
     202                 
     203                PtrToHand(str,&sampleHndl,sampleLen); 
     204                 
     205                err=AddMediaSample(theMedia,sampleHndl,0,sampleLen, sl->end_time - sl->begin_time,(SampleDescriptionHandle)textDesc, 1, 0, &sampleTime); 
     206                if (err != noErr) goto bail; 
     207                 
     208                ConvertTimeScale(&movieStartTime, movieTimeScale); 
     209                 
     210                err = InsertMediaIntoTrack(theTrack, movieStartTime.value.lo, sampleTime, sl->end_time - sl->begin_time, fixed1); 
     211                if (err != noErr) goto bail; 
     212                 
     213                DisposeHandle(sampleHndl); 
     214        } 
     215 
     216        sampleHndl = NULL; 
    213217 
    214218        if (*firstSubTrack == NULL) 
     
    220224 
    221225bail: 
     226        [serializer release]; 
     227        [pool release]; 
    222228        if (err) { 
    223229                if (theMedia) 
     
    231237                DisposeHandle((Handle) textDesc); 
    232238 
    233         if (data) 
    234                 DisposePtr(data); 
    235  
    236         if (dataHandler) 
    237                 CloseComponent(dataHandler); 
     239        DisposeHandle(dataRef); 
     240        if (sampleHndl) DisposeHandle(sampleHndl); 
     241 
    238242 
    239243        return err; 
     
    331335        return err; 
    332336} 
     337 
     338@implementation SubtitleSerializer 
     339-(id)init 
     340{ 
     341        if (self = [super init]) { 
     342                lines = [[NSMutableArray alloc] init]; 
     343                outpackets = [[NSMutableArray alloc] init]; 
     344                finished = NO; 
     345        } 
     346         
     347        return self; 
     348} 
     349 
     350-(void)addLine:(SubLine *)sline 
     351{ 
     352        [lines addObject:sline]; 
     353} 
     354 
     355-(void)addLineWithString:(NSString*)string start:(unsigned)start end:(unsigned)end 
     356{ 
     357        SubLine *sl = [[[SubLine alloc] init] autorelease]; 
     358         
     359        sl->line = [string retain]; 
     360        sl->begin_time = start; 
     361        sl->end_time = end; 
     362         
     363        [lines addObject:sl]; 
     364} 
     365 
     366static int cmp_line(id a, id b, void* unused) 
     367{                        
     368        SubLine *av = (SubLine*)a, *bv = (SubLine*)b; 
     369         
     370        if (av->begin_time > bv->begin_time) return NSOrderedDescbegining; 
     371        if (av->begin_time < bv->begin_time) return NSOrderedAscbegining; 
     372        return NSOrderedSame; 
     373} 
     374 
     375static int cmp_uint(const void *a, const void *b) 
     376{ 
     377        unsigned av = *(unsigned*)a, bv = *(unsigned*)b; 
     378         
     379        if (av > bv) return 1; 
     380        if (av < bv) return -1; 
     381        return 0; 
     382} 
     383 
     384static bool isinrange(unsigned base, unsigned test_s, unsigned test_e) 
     385{ 
     386        return (base >= test_s) && (base < test_e); 
     387} 
     388 
     389/* FIXME: This algorithm is not nearly good enough. 
     390   It works for trivial overlaps, but not [  [   ]   [   ]  ] (where [] are subtitle lines). 
     391   Many other cases may fail. */ 
     392 
     393-(void)refill 
     394{ 
     395        unsigned num = [lines count]; 
     396        unsigned times[num*2+1]; 
     397        SubLine *slines[num]; 
     398         
     399        [lines sortUsingFunction:cmp_line context:nil]; 
     400        [lines getObjects:slines]; 
     401        //NSLog(@"%@",lines); 
     402        for (int i=0;i < num;i++) { 
     403                times[i*2]   = slines[i]->begin_time; 
     404                times[i*2+1] = slines[i]->end_time; 
     405        } 
     406         
     407        times[num * 2] = times[num * 2 - 1]; 
     408         
     409        qsort(times, num*2, sizeof(unsigned), cmp_uint); 
     410         
     411        for (int i=0;i < num*2; i++) { 
     412                if (i > 0 && times[i-1] == times[i]) continue; 
     413                NSMutableString *accum = [NSMutableString string]; 
     414                unsigned start = times[i], end = times[i+1]; 
     415                bool startedOutput = false, finishedOutput = false; 
     416                 
     417                // Add on packets until we find one that marks it ending (by starting later) 
     418                // ...except if we know this is the last input packet from the stream, then we have to explicitly flush it 
     419                if (finished && i >= (num*2)-2) finishedOutput = true;  
     420                         
     421                for (int j=0; j < num; j++) { 
     422                        if (isinrange(times[i], slines[j]->begin_time, slines[j]->end_time)) { 
     423                                [accum appendString:slines[j]->line]; 
     424                                startedOutput = true; 
     425                        } else if (startedOutput) {finishedOutput = true; break;} 
     426                } 
     427                 
     428                 
     429                if (finishedOutput && startedOutput) { 
     430                        [accum deleteCharactersInRange:NSMakeRange([accum length] - 1, 1)]; // delete last newline 
     431                        SubLine *event = [[[SubLine alloc] initWithLine:accum start:start end:end] autorelease]; 
     432                         
     433                        [outpackets addObject:event]; 
     434                } 
     435        } 
     436         
     437        //NSLog(@"%@",outpackets); 
     438 
     439        [lines removeAllObjects]; 
     440        if (!finished) [lines addObject:slines[num-1]]; //keep the last packet in the queue 
     441} 
     442 
     443-(SubLine*)getSerializedPacket 
     444{ 
     445        if ([outpackets count] == 0) [self refill]; 
     446        if ([outpackets count] == 0) return nil; 
     447         
     448        SubLine *sl = [outpackets objectAtIndex:0]; 
     449        [outpackets removeObjectAtIndex:0]; 
     450         
     451        return sl; 
     452} 
     453 
     454-(void)setFinished:(BOOL)f 
     455{ 
     456        finished = f; 
     457} 
     458@end 
     459 
     460@implementation SubLine 
     461-(id)initWithLine:(NSString*)l start:(unsigned)s end:(unsigned)e 
     462{ 
     463        if (self = [super init]) { 
     464                line = [l retain]; 
     465                begin_time = s; 
     466                end_time = e; 
     467        } 
     468         
     469        return self; 
     470} 
     471 
     472-(void)dealloc 
     473{ 
     474        [line release]; 
     475        [super dealloc]; 
     476} 
     477 
     478-(NSString*)description 
     479{ 
     480        return [NSString stringWithFormat:@"\"%@\", %d -> %d",line,begin_time,end_time]; 
     481} 
     482@end