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