| 1 |
// |
|---|
| 2 |
// SubImport.m |
|---|
| 3 |
// SSARender2 |
|---|
| 4 |
// |
|---|
| 5 |
// Created by Alexander Strange on 7/24/07. |
|---|
| 6 |
// Copyright 2007 __MyCompanyName__. All rights reserved. |
|---|
| 7 |
// |
|---|
| 8 |
|
|---|
| 9 |
#include <QuickTime/QuickTime.h> |
|---|
| 10 |
#include "CommonUtils.h" |
|---|
| 11 |
#include "Codecprintf.h" |
|---|
| 12 |
#include "CodecIDs.h" |
|---|
| 13 |
#import "SubImport.h" |
|---|
| 14 |
#import "SubParsing.h" |
|---|
| 15 |
#import "SubUtilities.h" |
|---|
| 16 |
|
|---|
| 17 |
//#define SS_DEBUG |
|---|
| 18 |
|
|---|
| 19 |
#pragma mark C |
|---|
| 20 |
|
|---|
| 21 |
// if the subtitle filename is something like title.en.srt or movie.fre.srt |
|---|
| 22 |
// this function detects it and returns the subtitle language |
|---|
| 23 |
short GetFilenameLanguage(CFStringRef filename) |
|---|
| 24 |
{ |
|---|
| 25 |
CFRange findResult; |
|---|
| 26 |
CFStringRef baseName = NULL; |
|---|
| 27 |
CFStringRef langStr = NULL; |
|---|
| 28 |
short lang = langUnspecified; |
|---|
| 29 |
|
|---|
| 30 |
// find and strip the extension |
|---|
| 31 |
findResult = CFStringFind(filename, CFSTR("."), kCFCompareBackwards); |
|---|
| 32 |
findResult.length = findResult.location; |
|---|
| 33 |
findResult.location = 0; |
|---|
| 34 |
baseName = CFStringCreateWithSubstring(NULL, filename, findResult); |
|---|
| 35 |
|
|---|
| 36 |
// then find the previous period |
|---|
| 37 |
findResult = CFStringFind(baseName, CFSTR("."), kCFCompareBackwards); |
|---|
| 38 |
findResult.location++; |
|---|
| 39 |
findResult.length = CFStringGetLength(baseName) - findResult.location; |
|---|
| 40 |
|
|---|
| 41 |
// check for 3 char language code |
|---|
| 42 |
if (findResult.length == 3) { |
|---|
| 43 |
char langCStr[4] = ""; |
|---|
| 44 |
|
|---|
| 45 |
langStr = CFStringCreateWithSubstring(NULL, baseName, findResult); |
|---|
| 46 |
CFStringGetCString(langStr, langCStr, 4, kCFStringEncodingASCII); |
|---|
| 47 |
lang = ISO639_2ToQTLangCode(langCStr); |
|---|
| 48 |
|
|---|
| 49 |
CFRelease(langStr); |
|---|
| 50 |
|
|---|
| 51 |
// and for a 2 char language code |
|---|
| 52 |
} else if (findResult.length == 2) { |
|---|
| 53 |
char langCStr[3] = ""; |
|---|
| 54 |
|
|---|
| 55 |
langStr = CFStringCreateWithSubstring(NULL, baseName, findResult); |
|---|
| 56 |
CFStringGetCString(langStr, langCStr, 3, kCFStringEncodingASCII); |
|---|
| 57 |
lang = ISO639_1ToQTLangCode(langCStr); |
|---|
| 58 |
|
|---|
| 59 |
CFRelease(langStr); |
|---|
| 60 |
} |
|---|
| 61 |
|
|---|
| 62 |
CFRelease(baseName); |
|---|
| 63 |
return lang; |
|---|
| 64 |
} |
|---|
| 65 |
|
|---|
| 66 |
static bool ShouldEngageFrontRowHack(void) |
|---|
| 67 |
{ |
|---|
| 68 |
bool ret; |
|---|
| 69 |
Boolean isSet; |
|---|
| 70 |
|
|---|
| 71 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
|---|
| 72 |
NSString *applicationName = [[NSProcessInfo processInfo] processName]; |
|---|
| 73 |
long minorVersion; |
|---|
| 74 |
Gestalt(gestaltSystemVersionMinor, &minorVersion); |
|---|
| 75 |
CFPreferencesGetAppBooleanValue(CFSTR("PerianFrontRowSubtitleHack"),CFSTR("org.perian.Perian"),&isSet); |
|---|
| 76 |
|
|---|
| 77 |
ret = (minorVersion == 5) && [applicationName isEqualToString:@"Front Row"] && isSet; |
|---|
| 78 |
[pool release]; |
|---|
| 79 |
|
|---|
| 80 |
return ret; |
|---|
| 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 |
|
|---|
| 106 |
theTrack = NewMovieTrack(theMovie, trackWidth, trackHeight, kNoVolume); |
|---|
| 107 |
if (theTrack != NULL) { |
|---|
| 108 |
theMedia = NewTrackMedia(theTrack, VideoMediaType, timescale, dataRef, dataRefType); |
|---|
| 109 |
|
|---|
| 110 |
if (theMedia != NULL) { |
|---|
| 111 |
// finally, say that we're transparent |
|---|
| 112 |
MediaHandler mh = GetMediaHandler(theMedia); |
|---|
| 113 |
|
|---|
| 114 |
if (ShouldEngageFrontRowHack()) { |
|---|
| 115 |
RGBColor blendColor = {0x0000, 0x0000, 0x0000}; |
|---|
| 116 |
MediaSetGraphicsMode(mh, transparent, &blendColor); |
|---|
| 117 |
} |
|---|
| 118 |
else MediaSetGraphicsMode(mh, graphicsModePreBlackAlpha, NULL); |
|---|
| 119 |
|
|---|
| 120 |
// subtitle tracks should be above the video track, which should be layer 0 |
|---|
| 121 |
SetTrackLayer(theTrack, -1); |
|---|
| 122 |
} else { |
|---|
| 123 |
DisposeMovieTrack(theTrack); |
|---|
| 124 |
theTrack = NULL; |
|---|
| 125 |
} |
|---|
| 126 |
} |
|---|
| 127 |
|
|---|
| 128 |
return theTrack; |
|---|
| 129 |
} |
|---|
| 130 |
|
|---|
| 131 |
static NSString *MatroskaPacketizeLine(NSDictionary *sub, int n) |
|---|
| 132 |
{ |
|---|
| 133 |
NSString *name = [sub objectForKey:@"Name"]; |
|---|
| 134 |
if (!name) name = [sub objectForKey:@"Actor"]; |
|---|
| 135 |
|
|---|
| 136 |
return [NSString stringWithFormat:@"%d,%d,%@,%@,%@,%@,%@,%@,%@\n", |
|---|
| 137 |
n+1, |
|---|
| 138 |
[[sub objectForKey:@"Layer"] intValue], |
|---|
| 139 |
[sub objectForKey:@"Style"], |
|---|
| 140 |
[sub objectForKey:@"Name"], |
|---|
| 141 |
[sub objectForKey:@"MarginL"], |
|---|
| 142 |
[sub objectForKey:@"MarginR"], |
|---|
| 143 |
[sub objectForKey:@"MarginV"], |
|---|
| 144 |
[sub objectForKey:@"Effect"], |
|---|
| 145 |
[sub objectForKey:@"Text"]]; |
|---|
| 146 |
} |
|---|
| 147 |
|
|---|
| 148 |
static unsigned ParseSSATime(NSString *time) |
|---|
| 149 |
{ |
|---|
| 150 |
unsigned hour, minute, second, millisecond; |
|---|
| 151 |
|
|---|
| 152 |
sscanf([time UTF8String],"%u:%u:%u.%u",&hour,&minute,&second,&millisecond); |
|---|
| 153 |
|
|---|
| 154 |
return hour * 100 * 60 * 60 + minute * 100 * 60 + second * 100 + millisecond; |
|---|
| 155 |
} |
|---|
| 156 |
|
|---|
| 157 |
static NSString *LoadSSAFromPath(NSString *path, SubSerializer *ss) |
|---|
| 158 |
{ |
|---|
| 159 |
NSString *nssSub = STLoadFileWithUnknownEncoding(path); |
|---|
| 160 |
|
|---|
| 161 |
if (!nssSub) return nil; |
|---|
| 162 |
|
|---|
| 163 |
size_t slen = [nssSub length], flen = sizeof(unichar) * (slen+1); |
|---|
| 164 |
unichar *subdata = (unichar*)malloc(flen); |
|---|
| 165 |
[nssSub getCharacters:subdata]; |
|---|
| 166 |
|
|---|
| 167 |
if (subdata[slen-1] != '\n') subdata[slen++] = '\n'; // append newline if missing |
|---|
| 168 |
|
|---|
| 169 |
NSDictionary *headers; |
|---|
| 170 |
NSArray *subs; |
|---|
| 171 |
|
|---|
| 172 |
SubParseSSAFile(subdata, slen, &headers, NULL, &subs); |
|---|
| 173 |
free(subdata); |
|---|
| 174 |
|
|---|
| 175 |
int i, numlines = [subs count]; |
|---|
| 176 |
|
|---|
| 177 |
for (i = 0; i < numlines; i++) { |
|---|
| 178 |
NSDictionary *sub = [subs objectAtIndex:i]; |
|---|
| 179 |
SubLine *sl = [[SubLine alloc] initWithLine:MatroskaPacketizeLine(sub, i) |
|---|
| 180 |
start:ParseSSATime([sub objectForKey:@"Start"]) end:ParseSSATime([sub objectForKey:@"End"])]; |
|---|
| 181 |
|
|---|
| 182 |
[ss addLine:sl]; |
|---|
| 183 |
[sl autorelease]; |
|---|
| 184 |
} |
|---|
| 185 |
|
|---|
| 186 |
return [nssSub substringToIndex:[nssSub rangeOfString:@"[Events]" options:NSLiteralSearch].location]; |
|---|
| 187 |
} |
|---|
| 188 |
|
|---|
| 189 |
#pragma mark SAMI Parsing |
|---|
| 190 |
|
|---|
| 191 |
static void LoadSRTFromPath(NSString *path, SubSerializer *ss) |
|---|
| 192 |
{ |
|---|
| 193 |
NSMutableString *srt = STStandardizeStringNewlines(STLoadFileWithUnknownEncoding(path)); |
|---|
| 194 |
if (!srt) return; |
|---|
| 195 |
|
|---|
| 196 |
if ([srt characterAtIndex:0] == 0xFEFF) [srt deleteCharactersInRange:NSMakeRange(0,1)]; |
|---|
| 197 |
if ([srt characterAtIndex:[srt length]-1] != '\n') [srt appendFormat:@"%c",'\n']; |
|---|
| 198 |
|
|---|
| 199 |
NSScanner *sc = [NSScanner scannerWithString:srt]; |
|---|
| 200 |
NSString *res=nil; |
|---|
| 201 |
[sc setCharactersToBeSkipped:nil]; |
|---|
| 202 |
|
|---|
| 203 |
int h, m, s, ms; |
|---|
| 204 |
unsigned startTime=0, endTime=0; |
|---|
| 205 |
|
|---|
| 206 |
enum { |
|---|
| 207 |
INITIAL, |
|---|
| 208 |
TIMESTAMP, |
|---|
| 209 |
LINES |
|---|
| 210 |
} state = INITIAL; |
|---|
| 211 |
|
|---|
| 212 |
do { |
|---|
| 213 |
switch (state) { |
|---|
| 214 |
case INITIAL: |
|---|
| 215 |
if ([sc scanInt:NULL] == TRUE && [sc scanUpToString:@"\n" intoString:&res] == FALSE) { |
|---|
| 216 |
state = TIMESTAMP; |
|---|
| 217 |
[sc scanString:@"\n" intoString:nil]; |
|---|
| 218 |
} else |
|---|
| 219 |
[sc setScanLocation:[sc scanLocation]+1]; |
|---|
| 220 |
break; |
|---|
| 221 |
case TIMESTAMP: |
|---|
| 222 |
[sc scanInt:&h]; [sc scanString:@":" intoString:nil]; |
|---|
| 223 |
[sc scanInt:&m]; [sc scanString:@":" intoString:nil]; |
|---|
| 224 |
[sc scanInt:&s]; [sc scanString:@"," intoString:nil]; |
|---|
| 225 |
[sc scanInt:&ms]; [sc scanString:@" --> " intoString:nil]; |
|---|
| 226 |
startTime = ms + s*1000 + m*1000*60 + h*1000*60*60; |
|---|
| 227 |
[sc scanInt:&h]; [sc scanString:@":" intoString:nil]; |
|---|
| 228 |
[sc scanInt:&m]; [sc scanString:@":" intoString:nil]; |
|---|
| 229 |
[sc scanInt:&s]; [sc scanString:@"," intoString:nil]; |
|---|
| 230 |
[sc scanInt:&ms]; [sc scanString:@"\n" intoString:nil]; |
|---|
| 231 |
endTime = ms + s*1000 + m*1000*60 + h*1000*60*60; |
|---|
| 232 |
state = LINES; |
|---|
| 233 |
break; |
|---|
| 234 |
case LINES: |
|---|
| 235 |
[sc scanUpToString:@"\n\n" intoString:&res]; |
|---|
| 236 |
[sc scanString:@"\n\n" intoString:nil]; |
|---|
| 237 |
SubLine *sl = [[SubLine alloc] initWithLine:res start:startTime end:endTime]; |
|---|
| 238 |
[ss addLine:[sl autorelease]]; |
|---|
| 239 |
state = INITIAL; |
|---|
| 240 |
break; |
|---|
| 241 |
}; |
|---|
| 242 |
} while (![sc isAtEnd]); |
|---|
| 243 |
} |
|---|
| 244 |
|
|---|
| 245 |
static int parse_SYNC(NSString *str) |
|---|
| 246 |
{ |
|---|
| 247 |
NSScanner *sc = [NSScanner scannerWithString:str]; |
|---|
| 248 |
|
|---|
| 249 |
int res; |
|---|
| 250 |
|
|---|
| 251 |
if ([sc scanString:@"START=" intoString:nil]) |
|---|
| 252 |
[sc scanInt:&res]; |
|---|
| 253 |
|
|---|
| 254 |
return res; |
|---|
| 255 |
} |
|---|
| 256 |
|
|---|
| 257 |
static NSArray *parse_STYLE(NSString *str) |
|---|
| 258 |
{ |
|---|
| 259 |
NSScanner *sc = [NSScanner scannerWithString:str]; |
|---|
| 260 |
|
|---|
| 261 |
NSString *firstRes; |
|---|
| 262 |
NSString *secondRes; |
|---|
| 263 |
NSArray *subArray; |
|---|
| 264 |
int secondLoc; |
|---|
| 265 |
|
|---|
| 266 |
[sc scanUpToString:@"<P CLASS=" intoString:nil]; |
|---|
| 267 |
if ([sc scanString:@"<P CLASS=" intoString:nil]) |
|---|
| 268 |
[sc scanUpToString:@">" intoString:&firstRes]; |
|---|
| 269 |
else |
|---|
| 270 |
firstRes = @"noClass"; |
|---|
| 271 |
|
|---|
| 272 |
secondLoc = [str length] * .9; |
|---|
| 273 |
[sc setScanLocation:secondLoc]; |
|---|
| 274 |
|
|---|
| 275 |
[sc scanUpToString:@"<P CLASS=" intoString:nil]; |
|---|
| 276 |
if ([sc scanString:@"<P CLASS=" intoString:nil]) |
|---|
| 277 |
[sc scanUpToString:@">" intoString:&secondRes]; |
|---|
| 278 |
else |
|---|
| 279 |
secondRes = @"noClass"; |
|---|
| 280 |
|
|---|
| 281 |
if ([firstRes isEqualToString:secondRes]) |
|---|
| 282 |
secondRes = @"noClass"; |
|---|
| 283 |
|
|---|
| 284 |
subArray = [NSArray arrayWithObjects:firstRes, secondRes, nil]; |
|---|
| 285 |
|
|---|
| 286 |
return subArray; |
|---|
| 287 |
} |
|---|
| 288 |
|
|---|
| 289 |
static int parse_P(NSString *str, NSArray *subArray) |
|---|
| 290 |
{ |
|---|
| 291 |
NSScanner *sc = [NSScanner scannerWithString:str]; |
|---|
| 292 |
|
|---|
| 293 |
NSString *res; |
|---|
| 294 |
int subLang; |
|---|
| 295 |
|
|---|
| 296 |
if ([sc scanString:@"CLASS=" intoString:nil]) |
|---|
| 297 |
[sc scanUpToString:@">" intoString:&res]; |
|---|
| 298 |
else |
|---|
| 299 |
res = @"noClass"; |
|---|
| 300 |
|
|---|
| 301 |
if ([res isEqualToString:[subArray objectAtIndex:0]]) |
|---|
| 302 |
subLang = 1; |
|---|
| 303 |
else if ([res isEqualToString:[subArray objectAtIndex:1]]) |
|---|
| 304 |
subLang = 2; |
|---|
| 305 |
else |
|---|
| 306 |
subLang = 3; |
|---|
| 307 |
|
|---|
| 308 |
return subLang; |
|---|
| 309 |
} |
|---|
| 310 |
|
|---|
| 311 |
static NSString *parse_COLOR(NSString *str) |
|---|
| 312 |
{ |
|---|
| 313 |
NSString *cvalue; |
|---|
| 314 |
NSMutableString *cname = [NSMutableString stringWithFormat:@"%@", str]; |
|---|
| 315 |
|
|---|
| 316 |
if ([cname characterAtIndex:0] == '#' && [cname lengthOfBytesUsingEncoding:NSASCIIStringEncoding] == 7) |
|---|
| 317 |
cvalue = [NSString stringWithFormat:@"{\\1c&H%@%@%@&}", [cname substringWithRange:NSMakeRange(5,2)], [cname substringWithRange:NSMakeRange(3,2)], [cname substringWithRange:NSMakeRange(1,2)]]; |
|---|
| 318 |
else { |
|---|
| 319 |
[cname replaceOccurrencesOfString:@"Aqua" withString:@"00FFFF" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 320 |
[cname replaceOccurrencesOfString:@"Black" withString:@"000000" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 321 |
[cname replaceOccurrencesOfString:@"Blue" withString:@"0000FF" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 322 |
[cname replaceOccurrencesOfString:@"Fuchsia" withString:@"FF00FF" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 323 |
[cname replaceOccurrencesOfString:@"Gray" withString:@"808080" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 324 |
[cname replaceOccurrencesOfString:@"Green" withString:@"008000" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 325 |
[cname replaceOccurrencesOfString:@"Lime" withString:@"00FF00" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 326 |
[cname replaceOccurrencesOfString:@"Maroon" withString:@"800000" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 327 |
[cname replaceOccurrencesOfString:@"Navy" withString:@"000080" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 328 |
[cname replaceOccurrencesOfString:@"Olive" withString:@"808000" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 329 |
[cname replaceOccurrencesOfString:@"Purple" withString:@"800080" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 330 |
[cname replaceOccurrencesOfString:@"Red" withString:@"FF0000" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 331 |
[cname replaceOccurrencesOfString:@"Silver" withString:@"C0C0C0" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 332 |
[cname replaceOccurrencesOfString:@"Teal" withString:@"008080" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 333 |
[cname replaceOccurrencesOfString:@"White" withString:@"FFFFFF" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 334 |
[cname replaceOccurrencesOfString:@"Yellow" withString:@"FFFF00" options:1 range:NSMakeRange(0,[cname length])]; |
|---|
| 335 |
|
|---|
| 336 |
if ([cname lengthOfBytesUsingEncoding:NSASCIIStringEncoding] == 6) |
|---|
| 337 |
cvalue = [NSString stringWithFormat:@"{\\1c&H%@%@%@&}", [cname substringWithRange:NSMakeRange(4,2)], [cname substringWithRange:NSMakeRange(2,2)], [cname substringWithRange:NSMakeRange(0,2)]]; |
|---|
| 338 |
else |
|---|
| 339 |
cvalue = @"{\\1c&HFFFFFF&}"; |
|---|
| 340 |
} |
|---|
| 341 |
|
|---|
| 342 |
return cvalue; |
|---|
| 343 |
} |
|---|
| 344 |
|
|---|
| 345 |
static NSString *parse_FONT(NSString *str) |
|---|
| 346 |
{ |
|---|
| 347 |
NSScanner *sc = [NSScanner scannerWithString:str]; |
|---|
| 348 |
|
|---|
| 349 |
NSString *res; |
|---|
| 350 |
NSString *color; |
|---|
| 351 |
|
|---|
| 352 |
if ([sc scanString:@"COLOR=" intoString:nil]) { |
|---|
| 353 |
[sc scanUpToString:@">" intoString:&res]; |
|---|
| 354 |
color = parse_COLOR(res); |
|---|
| 355 |
} |
|---|
| 356 |
else |
|---|
| 357 |
color = @"{\\1c&HFFFFFF&}"; |
|---|
| 358 |
|
|---|
| 359 |
return color; |
|---|
| 360 |
} |
|---|
| 361 |
|
|---|
| 362 |
static NSMutableString *StandardizeSMIWhitespace(NSString *str) |
|---|
| 363 |
{ |
|---|
| 364 |
if (!str) return nil; |
|---|
| 365 |
NSMutableString *ms = [NSMutableString stringWithString:str]; |
|---|
| 366 |
[ms replaceOccurrencesOfString:@"\r" withString:@"" options:0 range:NSMakeRange(0,[ms length])]; |
|---|
| 367 |
[ms replaceOccurrencesOfString:@"\n" withString:@"" options:0 range:NSMakeRange(0,[ms length])]; |
|---|
| 368 |
[ms replaceOccurrencesOfString:@" " withString:@" " options:0 range:NSMakeRange(0,[ms length])]; |
|---|
| 369 |
return ms; |
|---|
| 370 |
} |
|---|
| 371 |
|
|---|
| 372 |
static void LoadSMIFromPath(NSString *path, SubSerializer *ss, int subCount) |
|---|
| 373 |
{ |
|---|
| 374 |
NSMutableString *smi = StandardizeSMIWhitespace(STLoadFileWithUnknownEncoding(path)); |
|---|
| 375 |
if (!smi) return; |
|---|
| 376 |
|
|---|
| 377 |
NSScanner *sc = [NSScanner scannerWithString:smi]; |
|---|
| 378 |
NSString *res = nil; |
|---|
| 379 |
[sc setCharactersToBeSkipped:nil]; |
|---|
| 380 |
[sc setCaseSensitive:NO]; |
|---|
| 381 |
|
|---|
| 382 |
NSMutableString *cmt = [NSMutableString stringWithFormat:@""]; |
|---|
| 383 |
NSArray *subLanguage = parse_STYLE(smi); |
|---|
| 384 |
|
|---|
| 385 |
int startTime=-1, endTime=-1, syncTime=-1; |
|---|
| 386 |
int cc=1; |
|---|
| 387 |
|
|---|
| 388 |
enum { |
|---|
| 389 |
TAG_INIT, |
|---|
| 390 |
TAG_SYNC, |
|---|
| 391 |
TAG_P, |
|---|
| 392 |
TAG_BR_OPEN, |
|---|
| 393 |
TAG_BR_CLOSE, |
|---|
| 394 |
TAG_B_OPEN, |
|---|
| 395 |
TAG_B_CLOSE, |
|---|
| 396 |
TAG_I_OPEN, |
|---|
| 397 |
TAG_I_CLOSE, |
|---|
| 398 |
TAG_FONT_OPEN, |
|---|
| 399 |
TAG_FONT_CLOSE, |
|---|
| 400 |
TAG_COMMENT |
|---|
| 401 |
} state = TAG_INIT; |
|---|
| 402 |
|
|---|
| 403 |
do { |
|---|
| 404 |
switch (state) { |
|---|
| 405 |
case TAG_INIT: |
|---|
| 406 |
[sc scanUpToString:@"<SYNC" intoString:nil]; |
|---|
| 407 |
if ([sc scanString:@"<SYNC" intoString:nil]) |
|---|
| 408 |
state = TAG_SYNC; |
|---|
| 409 |
break; |
|---|
| 410 |
case TAG_SYNC: |
|---|
| 411 |
[sc scanUpToString:@">" intoString:&res]; |
|---|
| 412 |
syncTime = parse_SYNC(res); |
|---|
| 413 |
if (startTime > -1) { |
|---|
| 414 |
endTime = syncTime; |
|---|
| 415 |
if (subCount == 2 && cc == 2) |
|---|
| 416 |
[cmt insertString:@"{\\an8}" atIndex:0]; |
|---|
| 417 |
if (subCount == 1 && cc == 1 || subCount == 2 && cc == 2) { |
|---|
| 418 |
SubLine *sl = [[SubLine alloc] initWithLine:cmt start:startTime end:endTime]; |
|---|
| 419 |
[ss addLine:[sl autorelease]]; |
|---|
| 420 |
} |
|---|
| 421 |
} |
|---|
| 422 |
startTime = syncTime; |
|---|
| 423 |
[cmt setString:@""]; |
|---|
| 424 |
state = TAG_COMMENT; |
|---|
| 425 |
break; |
|---|
| 426 |
case TAG_P: |
|---|
| 427 |
[sc scanUpToString:@">" intoString:&res]; |
|---|
| 428 |
cc = parse_P(res, subLanguage); |
|---|
| 429 |
[cmt setString:@""]; |
|---|
| 430 |
state = TAG_COMMENT; |
|---|
| 431 |
break; |
|---|
| 432 |
case TAG_BR_OPEN: |
|---|
| 433 |
[sc scanUpToString:@">" intoString:nil]; |
|---|
| 434 |
[cmt appendString:@"\\n"]; |
|---|
| 435 |
state = TAG_COMMENT; |
|---|
| 436 |
break; |
|---|
| 437 |
case TAG_BR_CLOSE: |
|---|
| 438 |
[sc scanUpToString:@">" intoString:nil]; |
|---|
| 439 |
[cmt appendString:@"\\n"]; |
|---|
| 440 |
state = TAG_COMMENT; |
|---|
| 441 |
break; |
|---|
| 442 |
case TAG_B_OPEN: |
|---|
| 443 |
[sc scanUpToString:@">" intoString:&res]; |
|---|
| 444 |
[cmt appendString:@"{\\b1}"]; |
|---|
| 445 |
state = TAG_COMMENT; |
|---|
| 446 |
break; |
|---|
| 447 |
case TAG_B_CLOSE: |
|---|
| 448 |
[sc scanUpToString:@">" intoString:nil]; |
|---|
| 449 |
[cmt appendString:@"{\\b0}"]; |
|---|
| 450 |
state = TAG_COMMENT; |
|---|
| 451 |
break; |
|---|
| 452 |
case TAG_I_OPEN: |
|---|
| 453 |
[sc scanUpToString:@">" intoString:&res]; |
|---|
| 454 |
[cmt appendString:@"{\\i1}"]; |
|---|
| 455 |
state = TAG_COMMENT; |
|---|
| 456 |
break; |
|---|
| 457 |
case TAG_I_CLOSE: |
|---|
| 458 |
[sc scanUpToString:@">" intoString:nil]; |
|---|
| 459 |
[cmt appendString:@"{\\i0}"]; |
|---|
| 460 |
state = TAG_COMMENT; |
|---|
| 461 |
break; |
|---|
| 462 |
case TAG_FONT_OPEN: |
|---|
| 463 |
[sc scanUpToString:@">" intoString:&res]; |
|---|
| 464 |
[cmt appendString:parse_FONT(res)]; |
|---|
| 465 |
state = TAG_COMMENT; |
|---|
| 466 |
break; |
|---|
| 467 |
case TAG_FONT_CLOSE: |
|---|
| 468 |
[sc scanUpToString:@">" intoString:nil]; |
|---|
| 469 |
[cmt appendString:@"{\\1c&HFFFFFF&}"]; |
|---|
| 470 |
state = TAG_COMMENT; |
|---|
| 471 |
break; |
|---|
| 472 |
case TAG_COMMENT: |
|---|
| 473 |
[sc scanString:@">" intoString:nil]; |
|---|
| 474 |
if ([sc scanUpToString:@"<" intoString:&res]) |
|---|
| 475 |
[cmt appendString:res]; |
|---|
| 476 |
else |
|---|
| 477 |
[cmt appendString:@"<>"]; |
|---|
| 478 |
if ([sc scanString:@"<" intoString:nil]) { |
|---|
| 479 |
if ([sc scanString:@"SYNC" intoString:nil]) { |
|---|
| 480 |
state = TAG_SYNC; |
|---|
| 481 |
break; |
|---|
| 482 |
} |
|---|
| 483 |
else if ([sc scanString:@"P" intoString:nil]) { |
|---|
| 484 |
state = TAG_P; |
|---|
| 485 |
break; |
|---|
| 486 |
} |
|---|
| 487 |
else if ([sc scanString:@"BR" intoString:nil]) { |
|---|
| 488 |
state = TAG_BR_OPEN; |
|---|
| 489 |
break; |
|---|
| 490 |
} |
|---|
| 491 |
else if ([sc scanString:@"/BR" intoString:nil]) { |
|---|
| 492 |
state = TAG_BR_CLOSE; |
|---|
| 493 |
break; |
|---|
| 494 |
} |
|---|
| 495 |
else if ([sc scanString:@"B" intoString:nil]) { |
|---|
| 496 |
state = TAG_B_OPEN; |
|---|
| 497 |
break; |
|---|
| 498 |
} |
|---|
| 499 |
else if ([sc scanString:@"/B" intoString:nil]) { |
|---|
| 500 |
state = TAG_B_CLOSE; |
|---|
| 501 |
break; |
|---|
| 502 |
} |
|---|
| 503 |
else if ([sc scanString:@"I" intoString:nil]) { |
|---|
| 504 |
state = TAG_I_OPEN; |
|---|
| 505 |
break; |
|---|
| 506 |
} |
|---|
| 507 |
else if ([sc scanString:@"/I" intoString:nil]) { |
|---|
| 508 |
state = TAG_I_CLOSE; |
|---|
| 509 |
break; |
|---|
| 510 |
} |
|---|
| 511 |
else if ([sc scanString:@"FONT" intoString:nil]) { |
|---|
| 512 |
state = TAG_FONT_OPEN; |
|---|
| 513 |
break; |
|---|
| 514 |
} |
|---|
| 515 |
else if ([sc scanString:@"/FONT" intoString:nil]) { |
|---|
| 516 |
state = TAG_FONT_CLOSE; |
|---|
| 517 |
break; |
|---|
| 518 |
} |
|---|
| 519 |
else { |
|---|
| 520 |
[cmt appendString:@"<"]; |
|---|
| 521 |
state = TAG_COMMENT; |
|---|
| 522 |
break; |
|---|
| 523 |
} |
|---|
| 524 |
} |
|---|
| 525 |
} |
|---|
| 526 |
} while (![sc isAtEnd]); |
|---|
| 527 |
} |
|---|
| 528 |
|
|---|
| 529 |
static ComponentResult LoadSingleTextSubtitle(const FSRef *theDirectory, CFStringRef filename, Movie theMovie, Track *firstSubTrack, int subtitleType, int whichTrack) |
|---|
| 530 |
{ |
|---|
| 531 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
|---|
| 532 |
UInt8 path[PATH_MAX]; |
|---|
| 533 |
|
|---|
| 534 |
FSRefMakePath(theDirectory, path, PATH_MAX); |
|---|
| 535 |
NSString *nsPath = [[NSString stringWithUTF8String:(char*)path] stringByAppendingPathComponent:(NSString*)filename]; |
|---|
| 536 |
|
|---|
| 537 |
SubSerializer *ss = [[SubSerializer alloc] init]; |
|---|
| 538 |
|
|---|
| 539 |
FourCharCode subCodec; |
|---|
| 540 |
Handle header = NULL; |
|---|
| 541 |
TimeScale timeBase; |
|---|
| 542 |
Fixed movieRate = fixed1; |
|---|
| 543 |
|
|---|
| 544 |
switch (subtitleType) { |
|---|
| 545 |
case kSubTypeASS: |
|---|
| 546 |
case kSubTypeSSA: |
|---|
| 547 |
default: |
|---|
| 548 |
{ |
|---|
| 549 |
timeBase = 100; |
|---|
| 550 |
subCodec = kSubFormatSSA; |
|---|
| 551 |
const char *cheader = [LoadSSAFromPath(nsPath, ss) UTF8String]; |
|---|
| 552 |
int headerLen = strlen(cheader); |
|---|
| 553 |
PtrToHand(cheader, &header, headerLen); |
|---|
| 554 |
} |
|---|
| 555 |
break; |
|---|
| 556 |
case kSubTypeSRT: |
|---|
| 557 |
timeBase = 1000; |
|---|
| 558 |
subCodec = kSubFormatUTF8; |
|---|
| 559 |
LoadSRTFromPath(nsPath, ss); |
|---|
| 560 |
break; |
|---|
| 561 |
case kSubTypeSMI: |
|---|
| 562 |
timeBase = 1000; |
|---|
| 563 |
subCodec = kSubFormatUTF8; |
|---|
| 564 |
LoadSMIFromPath(nsPath, ss, whichTrack); |
|---|
| 565 |
break; |
|---|
| 566 |
} |
|---|
| 567 |
|
|---|
| 568 |
[ss setFinished:YES]; |
|---|
| 569 |
|
|---|
| 570 |
SubPrerollFromHeader(header ? *header : NULL, header ? GetHandleSize(header) : 0); |
|---|
| 571 |
|
|---|
| 572 |
Handle dataRefHndl = NewHandleClear(sizeof(Handle) + 1); |
|---|
| 573 |
UInt32 emptyDataRefExt[2] = {EndianU32_NtoB(sizeof(UInt32)*2), EndianU32_NtoB(kDataRefExtensionInitializationData)}; |
|---|
| 574 |
PtrAndHand(emptyDataRefExt, dataRefHndl, sizeof(emptyDataRefExt)); |
|---|
| 575 |
|
|---|
| 576 |
Rect movieBox; |
|---|
| 577 |
GetMovieBox(theMovie, &movieBox); |
|---|
| 578 |
|
|---|
| 579 |
ImageDescriptionHandle textDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription)); |
|---|
| 580 |
|
|---|
| 581 |
Track theTrack = CreatePlaintextSubTrack(theMovie, textDesc, timeBase, dataRefHndl, HandleDataHandlerSubType, subCodec, header, movieBox); |
|---|
| 582 |
Media theMedia = NULL; |
|---|
| 583 |
ComponentResult err=noErr; |
|---|
| 584 |
TimeScale movieTimeScale = GetMovieTimeScale(theMovie); |
|---|
| 585 |
|
|---|
| 586 |
if (theTrack == NULL) { |
|---|
| 587 |
err = GetMoviesError(); |
|---|
| 588 |
goto bail; |
|---|
| 589 |
} |
|---|
| 590 |
|
|---|
| 591 |
theMedia = GetTrackMedia(theTrack); |
|---|
| 592 |
if (theMedia == NULL) { |
|---|
| 593 |
err = GetMoviesError(); |
|---|
| 594 |
goto bail; |
|---|
| 595 |
} |
|---|
| 596 |
|
|---|
| 597 |
BeginMediaEdits(theMedia); |
|---|
| 598 |
|
|---|
| 599 |
while (![ss isEmpty]) { |
|---|
| 600 |
SubLine *sl = [ss getSerializedPacket]; |
|---|
| 601 |
TimeRecord startTime = {SInt64ToWide(sl->begin_time), timeBase, 0}; |
|---|
| 602 |
TimeValue sampleTime; |
|---|
| 603 |
const char *str = [sl->line UTF8String]; |
|---|
| 604 |
unsigned sampleLen = strlen(str); |
|---|
| 605 |
Handle sampleHndl; |
|---|
| 606 |
|
|---|
| 607 |
PtrToHand(str, &sampleHndl, sampleLen); |
|---|
| 608 |
err = AddMediaSample(theMedia, sampleHndl, 0, sampleLen, sl->end_time - sl->begin_time, (SampleDescriptionHandle)textDesc, 1, 0, &sampleTime); |
|---|
| 609 |
|
|---|
| 610 |
if (err) { |
|---|
| 611 |
err = GetMoviesError(); |
|---|
| 612 |
Codecprintf(stderr,"error %d adding line from %d to %d in external subtitles",err,sl->begin_time,sl->end_time); |
|---|
| 613 |
} else { |
|---|
| 614 |
ConvertTimeScale(&startTime, movieTimeScale); |
|---|
| 615 |
InsertMediaIntoTrack(theTrack, startTime.value.lo, sampleTime, sl->end_time - sl->begin_time, movieRate); |
|---|
| 616 |
} |
|---|
| 617 |
|
|---|
| 618 |
err = noErr; |
|---|
| 619 |
DisposeHandle(sampleHndl); |
|---|
| 620 |
} |
|---|
| 621 |
|
|---|
| 622 |
EndMediaEdits(theMedia); |
|---|
| 623 |
|
|---|
| 624 |
if (*firstSubTrack == NULL) |
|---|
| 625 |
*firstSubTrack = theTrack; |
|---|
| 626 |
else |
|---|
| 627 |
SetTrackAlternate(*firstSubTrack, theTrack); |
|---|
| 628 |
|
|---|
| 629 |
SetMediaLanguage(theMedia, GetFilenameLanguage(filename)); |
|---|
| 630 |
|
|---|
| 631 |
bail: |
|---|
| 632 |
[ss release]; |
|---|
| 633 |
[pool release]; |
|---|
| 634 |
|
|---|
| 635 |
if (err) { |
|---|
| 636 |
if (theMedia) |
|---|
| 637 |
DisposeTrackMedia(theMedia); |
|---|
| 638 |
|
|---|
| 639 |
if (theTrack) |
|---|
| 640 |
DisposeMovieTrack(theTrack); |
|---|
| 641 |
} |
|---|
| 642 |
|
|---|
| 643 |
if (textDesc) DisposeHandle((Handle)textDesc); |
|---|
| 644 |
if (header) DisposeHandle((Handle)header); |
|---|
| 645 |
if (dataRefHndl) DisposeHandle((Handle)dataRefHndl); |
|---|
| 646 |
|
|---|
| 647 |
return err; |
|---|
| 648 |
} |
|---|
| 649 |
|
|---|
| 650 |
static ComponentResult LoadVobSubSubtitles(const FSRef *theDirectory, CFStringRef filename, Movie theMovie, Track *firstSubTrack) |
|---|
| 651 |
{ |
|---|
| 652 |
ComponentResult err = noErr; |
|---|
| 653 |
|
|---|
| 654 |
return err; |
|---|
| 655 |
} |
|---|
| 656 |
|
|---|
| 657 |
static Boolean ShouldLoadExternalSubtitles() |
|---|
| 658 |
{ |
|---|
| 659 |
Boolean isSet, value; |
|---|
| 660 |
|
|---|
| 661 |
value = CFPreferencesGetAppBooleanValue(CFSTR("LoadExternalSubtitles"),CFSTR("org.perian.Perian"),&isSet); |
|---|
| 662 |
|
|---|
| 663 |
return isSet ? value : YES; |
|---|
| 664 |
} |
|---|
| 665 |
|
|---|
| 666 |
ComponentResult LoadExternalSubtitles(const FSRef *theFile, Movie theMovie) |
|---|
| 667 |
{ |
|---|
| 668 |
ComponentResult err = noErr; |
|---|
| 669 |
Track firstSubTrack = NULL; |
|---|
| 670 |
HFSUniStr255 hfsFilename; |
|---|
| 671 |
CFStringRef cfFilename = NULL; |
|---|
| 672 |
FSRef parentDir; |
|---|
| 673 |
FSIterator dirItr = NULL; |
|---|
| 674 |
CFRange baseFilenameRange; |
|---|
| 675 |
ItemCount filesFound; |
|---|
| 676 |
Boolean containerChanged; |
|---|
| 677 |
|
|---|
| 678 |
if (!ShouldLoadExternalSubtitles()) return noErr; |
|---|
| 679 |
|
|---|
| 680 |
err = FSGetCatalogInfo(theFile, kFSCatInfoNone, NULL, &hfsFilename, NULL, &parentDir); |
|---|
| 681 |
if (err) goto bail; |
|---|
| 682 |
|
|---|
| 683 |
// find the location of the extension |
|---|
| 684 |
cfFilename = CFStringCreateWithCharacters(NULL, hfsFilename.unicode, hfsFilename.length); |
|---|
| 685 |
baseFilenameRange = CFStringFind(cfFilename, CFSTR("."), kCFCompareBackwards); |
|---|
| 686 |
|
|---|
| 687 |
// strip the extension |
|---|
| 688 |
if (baseFilenameRange.location != kCFNotFound) { |
|---|
| 689 |
CFStringRef temp = cfFilename; |
|---|
| 690 |
baseFilenameRange.length = baseFilenameRange.location; |
|---|
| 691 |
baseFilenameRange.location = 0; |
|---|
| 692 |
cfFilename = CFStringCreateWithSubstring(NULL, temp, baseFilenameRange); |
|---|
| 693 |
CFRelease(temp); |
|---|
| 694 |
} |
|---|
| 695 |
|
|---|
| 696 |
err = FSOpenIterator(&parentDir, kFSIterateFlat, &dirItr); |
|---|
| 697 |
if (err) goto bail; |
|---|
| 698 |
|
|---|
| 699 |
do { |
|---|
| 700 |
FSRef foundFileRef; |
|---|
| 701 |
HFSUniStr255 hfsFoundFilename; |
|---|
| 702 |
CFStringRef cfFoundFilename = NULL; |
|---|
| 703 |
CFComparisonResult cmpRes; |
|---|
| 704 |
|
|---|
| 705 |
err = FSGetCatalogInfoBulk(dirItr, 1, &filesFound, &containerChanged, kFSCatInfoNone, |
|---|
| 706 |
NULL, &foundFileRef, NULL, &hfsFoundFilename); |
|---|
| 707 |
if (err) goto bail; |
|---|
| 708 |
|
|---|
| 709 |
cfFoundFilename = CFStringCreateWithCharacters(NULL, hfsFoundFilename.unicode, hfsFoundFilename.length); |
|---|
| 710 |
cmpRes = CFStringCompareWithOptions(cfFoundFilename, cfFilename, baseFilenameRange, kCFCompareCaseInsensitive); |
|---|
| 711 |
|
|---|
| 712 |
// two files share the same base, so now check the extension of the found file |
|---|
| 713 |
if (cmpRes == kCFCompareEqualTo) { |
|---|
| 714 |
CFRange extRange = { CFStringGetLength(cfFoundFilename) - 4, 4 }; |
|---|
| 715 |
CFRange actRange; |
|---|
| 716 |
int subType = -1; |
|---|
| 717 |
|
|---|
| 718 |
// SubRip |
|---|
| 719 |
actRange = CFStringFind(cfFoundFilename, CFSTR(".srt"), kCFCompareCaseInsensitive | kCFCompareBackwards); |
|---|
| 720 |
if (actRange.length && actRange.location == extRange.location) |
|---|
| 721 |
subType = kSubTypeSRT; |
|---|
| 722 |
else { |
|---|
| 723 |
// SubStationAlpha |
|---|
| 724 |
actRange = CFStringFind(cfFoundFilename, CFSTR(".ass"), kCFCompareCaseInsensitive | kCFCompareBackwards); |
|---|
| 725 |
if (actRange.length && actRange.location == extRange.location) |
|---|
| 726 |
subType = kSubTypeASS; |
|---|
| 727 |
else { |
|---|
| 728 |
actRange = CFStringFind(cfFoundFilename, CFSTR(".ssa"), kCFCompareCaseInsensitive | kCFCompareBackwards); |
|---|
| 729 |
if (actRange.length && actRange.location == extRange.location) |
|---|
| 730 |
subType = kSubTypeSSA; |
|---|
| 731 |
else { |
|---|
| 732 |
// VobSub |
|---|
| 733 |
actRange = CFStringFind(cfFoundFilename, CFSTR(".idx"), kCFCompareCaseInsensitive | kCFCompareBackwards); |
|---|
| 734 |
if (actRange.length && actRange.location == extRange.location) |
|---|
| 735 |
err = LoadVobSubSubtitles(&parentDir, cfFoundFilename, theMovie, &firstSubTrack); |
|---|
| 736 |
else { |
|---|
| 737 |
actRange = CFStringFind(cfFoundFilename, CFSTR(".smi"), kCFCompareCaseInsensitive | kCFCompareBackwards); |
|---|
| 738 |
if (actRange.length && actRange.location == extRange.location) |
|---|
| 739 |
subType = kSubTypeSMI; |
|---|
| 740 |
} |
|---|
| 741 |
} |
|---|
| 742 |
} |
|---|
| 743 |
} |
|---|
| 744 |
|
|---|
| 745 |
if (subType == kSubTypeSMI) { |
|---|
| 746 |
err = LoadSingleTextSubtitle(&parentDir, cfFoundFilename, theMovie, &firstSubTrack, kSubTypeSMI, 1); |
|---|
| 747 |
if (!err) err = LoadSingleTextSubtitle(&parentDir, cfFoundFilename, theMovie, &firstSubTrack, kSubTypeSMI, 2); |
|---|
| 748 |
} |
|---|
| 749 |
else if (subType != -1) |
|---|
| 750 |
err = LoadSingleTextSubtitle(&parentDir, cfFoundFilename, theMovie, &firstSubTrack, subType, 0); |
|---|
| 751 |
|
|---|
| 752 |
if (err) goto bail; |
|---|
| 753 |
} |
|---|
| 754 |
|
|---|
| 755 |
CFRelease(cfFoundFilename); |
|---|
| 756 |
} while (filesFound && !containerChanged); |
|---|
| 757 |
|
|---|
| 758 |
bail: |
|---|
| 759 |
if (err == errFSNoMoreItems) |
|---|
| 760 |
err = noErr; |
|---|
| 761 |
|
|---|
| 762 |
if (dirItr) |
|---|
| 763 |
FSCloseIterator(dirItr); |
|---|
| 764 |
|
|---|
| 765 |
if (cfFilename) |
|---|
| 766 |
CFRelease(cfFilename); |
|---|
| 767 |
|
|---|
| 768 |
return err; |
|---|
| 769 |
} |
|---|
| 770 |
|
|---|
| 771 |
ComponentResult LoadExternalSubtitlesFromFileDataRef(Handle dataRef, OSType dataRefType, Movie theMovie) |
|---|
| 772 |
{ |
|---|
| 773 |
if (dataRefType != AliasDataHandlerSubType) return noErr; |
|---|
| 774 |
|
|---|
| 775 |
CFStringRef cfPath; |
|---|
| 776 |
FSRef ref; |
|---|
| 777 |
uint8_t path[PATH_MAX]; |
|---|
| 778 |
|
|---|
| 779 |
QTGetDataReferenceFullPathCFString(dataRef, dataRefType, kQTPOSIXPathStyle, &cfPath); |
|---|
| 780 |
CFStringGetCString(cfPath, (char*)path, PATH_MAX, kCFStringEncodingUTF8); |
|---|
| 781 |
FSPathMakeRef(path, &ref, NULL); |
|---|
| 782 |
CFRelease(cfPath); |
|---|
| 783 |
|
|---|
| 784 |
return LoadExternalSubtitles(&ref, theMovie); |
|---|
| 785 |
} |
|---|
| 786 |
|
|---|
| 787 |
#pragma mark Obj-C Classes |
|---|
| 788 |
|
|---|
| 789 |
@implementation SubSerializer |
|---|
| 790 |
-(id)init |
|---|
| 791 |
{ |
|---|
| 792 |
if (self = [super init]) { |
|---|
| 793 |
lines = [[NSMutableArray alloc] init]; |
|---|
| 794 |
outpackets = [[NSMutableArray alloc] init]; |
|---|
| 795 |
finished = NO; |
|---|
| 796 |
write_gap = NO; |
|---|
| 797 |
toReturn = nil; |
|---|
| 798 |
last_time = 0; |
|---|
| 799 |
} |
|---|
| 800 |
|
|---|
| 801 |
return self; |
|---|
| 802 |
} |
|---|
| 803 |
|
|---|
| 804 |
-(void)dealloc |
|---|
| 805 |
{ |
|---|
| 806 |
[outpackets release]; |
|---|
| 807 |
[lines release]; |
|---|
| 808 |
[super dealloc]; |
|---|
| 809 |
} |
|---|
| 810 |
|
|---|
| 811 |
-(void)addLine:(SubLine *)sline |
|---|
| 812 |
{ |
|---|
| 813 |
if (sline->begin_time < sline->end_time) |
|---|
| 814 |
[lines addObject:sline]; |
|---|
| 815 |
} |
|---|
| 816 |
|
|---|
| 817 |
static int cmp_line(id a, id b, void* unused) |
|---|
| 818 |
{ |
|---|
| 819 |
SubLine *av = (SubLine*)a, *bv = (SubLine*)b; |
|---|
| 820 |
|
|---|
| 821 |
if (av->begin_time > bv->begin_time) return NSOrderedDescending; |
|---|
| 822 |
if (av->begin_time < bv->begin_time) return NSOrderedAscending; |
|---|
| 823 |
return NSOrderedSame; |
|---|
| 824 |
} |
|---|
| 825 |
|
|---|
| 826 |
static int cmp_uint(const void *a, const void *b) |
|---|
| 827 |
{ |
|---|
| 828 |
unsigned av = *(unsigned*)a, bv = *(unsigned*)b; |
|---|
| 829 |
|
|---|
| 830 |
if (av > bv) return 1; |
|---|
| 831 |
if (av < bv) return -1; |
|---|
| 832 |
return 0; |
|---|
| 833 |
} |
|---|
| 834 |
|
|---|
| 835 |
static bool isinrange(unsigned base, unsigned test_s, unsigned test_e) |
|---|
| 836 |
{ |
|---|
| 837 |
return (base >= test_s) && (base < test_e); |
|---|
| 838 |
} |
|---|
| 839 |
|
|---|
| 840 |
-(void)refill |
|---|
| 841 |
{ |
|---|
| 842 |
unsigned num = [lines count]; |
|---|
| 843 |
unsigned min_allowed = finished ? 1 : 2; |
|---|
| 844 |
if (num < min_allowed) return; |
|---|
| 845 |
unsigned times[num*2], last_last_end = 0; |
|---|
| 846 |
SubLine *slines[num], *last=nil; |
|---|
| 847 |
bool last_has_invalid_end = false; |
|---|
| 848 |
|
|---|
| 849 |
[lines sortUsingFunction:cmp_line context:nil]; |
|---|
| 850 |
[lines getObjects:slines]; |
|---|
| 851 |
#ifdef SS_DEBUG |
|---|
| 852 |
NSLog(@"pre - %@",lines); |
|---|
| 853 |
#endif |
|---|
| 854 |
//leave early if all subtitle lines overlap |
|---|
| 855 |
if (!finished) { |
|---|
| 856 |
bool all_overlap = true; |
|---|
| 857 |
int i; |
|---|
| 858 |
|
|---|
| 859 |
for (i=0;i < num-1;i++) { |
|---|
| 860 |
SubLine *c = slines[i], *n = slines[i+1]; |
|---|
| 861 |
if (c->end_time <= n->begin_time) {all_overlap = false; break;} |
|---|
| 862 |
} |
|---|
| 863 |
|
|---|
| 864 |
if (all_overlap) return; |
|---|
| 865 |
|
|---|
| 866 |
for (i=0;i < num-1;i++) { |
|---|
| 867 |
if (isinrange(slines[num-1]->begin_time, slines[i]->begin_time, slines[i]->end_time)) { |
|---|
| 868 |
num = i + 1; break; |
|---|
| 869 |
} |
|---|
| 870 |
} |
|---|
| 871 |
} |
|---|
| 872 |
|
|---|
| 873 |
for (int i=0;i < num;i++) { |
|---|
| 874 |
times[i*2] = slines[i]->begin_time; |
|---|
| 875 |
times[i*2+1] = slines[i]->end_time; |
|---|
| 876 |
} |
|---|
| 877 |
|
|---|
| 878 |
qsort(times, num*2, sizeof(unsigned), cmp_uint); |
|---|
| 879 |
|
|---|
| 880 |
for (int i=0;i < num*2; i++) { |
|---|
| 881 |
if (i > 0 && times[i-1] == times[i]) continue; |
|---|
| 882 |
NSMutableString *accum = nil; |
|---|
| 883 |
unsigned start = times[i], last_end = start, next_start=times[num*2-1], end = start; |
|---|
| 884 |
bool finishedOutput = false, is_last_line = false; |
|---|
| 885 |
|
|---|
| 886 |
&nb |
|---|