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