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