| 1 | /* |
|---|
| 2 | * SSARenderCodec.m |
|---|
| 3 | * Copyright (c) 2007 Perian Project |
|---|
| 4 | * |
|---|
| 5 | * This program is free software; you can redistribute it and/or |
|---|
| 6 | * modify it under the terms of the GNU Lesser General Public |
|---|
| 7 | * License as published by the Free Software Foundation; |
|---|
| 8 | * version 2.1 of the License. |
|---|
| 9 | * |
|---|
| 10 | * This program is distributed in the hope that it will be useful, |
|---|
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 13 | * Lesser General Public License for more details. |
|---|
| 14 | * |
|---|
| 15 | * You should have received a copy of the GNU Lesser General Public |
|---|
| 16 | * License along with this program; if not, write to the Free Software |
|---|
| 17 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
|---|
| 18 | * |
|---|
| 19 | */ |
|---|
| 20 | |
|---|
| 21 | #import "SSADocument.h" |
|---|
| 22 | #import "Categories.h" |
|---|
| 23 | #include "SubImport.h" |
|---|
| 24 | |
|---|
| 25 | @implementation SSADocument |
|---|
| 26 | |
|---|
| 27 | // This line contains the default style, for SRT files. |
|---|
| 28 | |
|---|
| 29 | static ssastyleline SSA_DefaultStyle = (ssastyleline){ |
|---|
| 30 | @"Default",@"Helvetica", |
|---|
| 31 | 32 * (96./72.), |
|---|
| 32 | {{1,1,1,1},{1,1,1,1},{0,0,0,1},{0,0,0,1}}, // white on black |
|---|
| 33 | 1,0,0,0, |
|---|
| 34 | 100,100,0,0, |
|---|
| 35 | 0,1.5,1.5, |
|---|
| 36 | 2,1,0, |
|---|
| 37 | 10,10,10, |
|---|
| 38 | NULL, NULL |
|---|
| 39 | }; |
|---|
| 40 | |
|---|
| 41 | -(void)dealloc |
|---|
| 42 | { |
|---|
| 43 | int i; |
|---|
| 44 | if (disposedefaultstyle) free(defaultstyle); |
|---|
| 45 | for (i=0; i < stylecount; i++) {ATSUDisposeStyle(styles[i].atsustyle); ATSUDisposeTextLayout(styles[i].layout);} |
|---|
| 46 | |
|---|
| 47 | [_lines release]; |
|---|
| 48 | [header release]; |
|---|
| 49 | [super dealloc]; |
|---|
| 50 | } |
|---|
| 51 | |
|---|
| 52 | static ATSURGBAlphaColor SSAParseColor(NSString *c) |
|---|
| 53 | { |
|---|
| 54 | const char *c_ = [c UTF8String]; |
|---|
| 55 | unsigned char r, g, b, a; |
|---|
| 56 | unsigned int rgb; |
|---|
| 57 | |
|---|
| 58 | if (c_[0] == '&') { |
|---|
| 59 | rgb = strtoul(&c_[2],NULL,16); |
|---|
| 60 | } else { |
|---|
| 61 | rgb = strtol(c_,NULL,0); |
|---|
| 62 | } |
|---|
| 63 | |
|---|
| 64 | a = (rgb >> 24) & 0xff; |
|---|
| 65 | b = (rgb >> 16) & 0xff; |
|---|
| 66 | g = (rgb >> 8) & 0xff; |
|---|
| 67 | r = rgb & 0xff; |
|---|
| 68 | |
|---|
| 69 | a = 255-a; |
|---|
| 70 | |
|---|
| 71 | return (ATSURGBAlphaColor){(float)r/255.,(float)g/255.,(float)b/255.,(float)a/255.}; |
|---|
| 72 | } |
|---|
| 73 | |
|---|
| 74 | -(void)setupHeaders:(NSDictionary*)hDict width:(float)width height:(float)height |
|---|
| 75 | { |
|---|
| 76 | NSString *field; |
|---|
| 77 | float rX=-1, rY=-1, aspect = width / height; |
|---|
| 78 | if (field = [hDict objectForKey:@"PlayResX"]) rX = [field doubleValue]; |
|---|
| 79 | if (field = [hDict objectForKey:@"PlayResY"]) rY = [field doubleValue]; |
|---|
| 80 | if (rX > 0 && rY == -1) { |
|---|
| 81 | rY = rX / aspect; |
|---|
| 82 | } else if (rX == -1 && rY > 0) { |
|---|
| 83 | rX = rY * aspect; |
|---|
| 84 | } else if (rX == -1 && rY == -1) { |
|---|
| 85 | rX = 384; //magic numbers are ssa defaults |
|---|
| 86 | rY = 288; |
|---|
| 87 | } |
|---|
| 88 | |
|---|
| 89 | resX = rX; resY = rY; |
|---|
| 90 | |
|---|
| 91 | timescale = (field = [hDict objectForKey:@"Timer"])? [field doubleValue] / 100. : 1.; |
|---|
| 92 | collisiontype = Normal; |
|---|
| 93 | if ((field = [hDict objectForKey:@"Collisions"]) && [field isEqualToString:@"Reverse"]) collisiontype = Reverse; |
|---|
| 94 | if (field = [hDict objectForKey:@"ScriptType"]) { |
|---|
| 95 | if ([field isEqualToString:@"v4.00+"]) version = S_ASS; |
|---|
| 96 | else version = S_SSA; |
|---|
| 97 | } |
|---|
| 98 | } |
|---|
| 99 | |
|---|
| 100 | int SSA2ASSAlignment(int a) |
|---|
| 101 | { |
|---|
| 102 | if (a >= 7 && a <= 9) return a-3; |
|---|
| 103 | if (a >= 4 && a <= 6) return a+3; |
|---|
| 104 | if (a > 9 || a < 1) return 2; |
|---|
| 105 | return a; |
|---|
| 106 | } |
|---|
| 107 | |
|---|
| 108 | -(void) makeATSUStylesForSSAStyle:(ssastyleline *)s |
|---|
| 109 | { |
|---|
| 110 | { |
|---|
| 111 | ATSUAttributeTag tags[] = {kATSUFontTag, kATSUFontMatrixTag, kATSUStyleRenderingOptionsTag, kATSUSizeTag, kATSUTrackingTag, kATSUQDBoldfaceTag, kATSUQDItalicTag, kATSUQDUnderlineTag, kATSUStyleStrikeThroughTag}; |
|---|
| 112 | ByteCount sizes[] = {sizeof(ATSUFontID), sizeof(CGAffineTransform), sizeof(ATSStyleRenderingOptions), sizeof(Fixed), sizeof(Fixed), sizeof(Boolean), sizeof(Boolean), sizeof(Boolean), sizeof(Boolean)}; |
|---|
| 113 | ATSUFontID font; |
|---|
| 114 | CGAffineTransform matrix; |
|---|
| 115 | ATSStyleRenderingOptions opt = kATSStyleApplyAntiAliasing; |
|---|
| 116 | Fixed size, tracking; |
|---|
| 117 | |
|---|
| 118 | ATSUAttributeValuePtr vals[] = {&font, &matrix, &opt, &size, &tracking, &s->bold, &s->italic, &s->underline, &s->strikeout}; |
|---|
| 119 | char fname[256] = {0}; |
|---|
| 120 | ByteCount fl = 256; |
|---|
| 121 | |
|---|
| 122 | font = FMGetFontFromATSFontRef(ATSFontFindFromName((CFStringRef)s->font,kATSOptionFlagsDefault)); |
|---|
| 123 | //ATSUFindFontName(font,kFontFullName,kFontMacintoshPlatform,kFontNoScriptCode,kFontNoLanguage,fl,(char*)fname,&fl,NULL); |
|---|
| 124 | //kFontUnicodePlatform and MicrosoftPlatform are both pretty broken. asian fonts aren't found sometimes. this is an apple bug. |
|---|
| 125 | |
|---|
| 126 | if (font == kInvalidFont) |
|---|
| 127 | font = FMGetFontFromATSFontRef(ATSFontFindFromName((CFStringRef)@"Helvetica",kATSOptionFlagsDefault)); |
|---|
| 128 | //else NSLog(@"found font \"%s\" for name \"%@\"",fname,s->font); |
|---|
| 129 | |
|---|
| 130 | matrix = CGAffineTransformMakeScale(s->scalex/100.,s->scaley/100.); |
|---|
| 131 | |
|---|
| 132 | float scalesize = s->fsize * (72./96.); |
|---|
| 133 | size = FloatToFixed(scalesize); // scale from Windows 96 dpi size |
|---|
| 134 | |
|---|
| 135 | tracking = FloatToFixed(s->tracking); // i am really not sure about this! |
|---|
| 136 | |
|---|
| 137 | ATSUCreateStyle(&s->atsustyle); |
|---|
| 138 | ATSUSetAttributes(s->atsustyle,sizeof(vals) / sizeof(ATSUAttributeValuePtr),tags,sizes,vals); |
|---|
| 139 | |
|---|
| 140 | ATSUFontFeatureType ftype[] = {kLigaturesType,kLigaturesType}; |
|---|
| 141 | ATSUFontFeatureSelector fsel[] = {kCommonLigaturesOnSelector,kRareLigaturesOnSelector}; |
|---|
| 142 | |
|---|
| 143 | ATSUSetFontFeatures(s->atsustyle,sizeof(fsel) / sizeof(ATSUFontFeatureSelector),ftype,fsel); |
|---|
| 144 | } |
|---|
| 145 | |
|---|
| 146 | { |
|---|
| 147 | ATSUAttributeTag tags[] = {kATSULineFlushFactorTag, kATSULineRotationTag, kATSULineWidthTag}; |
|---|
| 148 | ByteCount sizes[] = {sizeof(Fract), sizeof(Fract), sizeof(ATSUTextMeasurement)}; |
|---|
| 149 | Fract alignment, rotation = FloatToFract(s->angle); |
|---|
| 150 | ATSUTextMeasurement width; |
|---|
| 151 | ATSUAttributeValuePtr vals[] = {&alignment, &rotation, &width}; |
|---|
| 152 | |
|---|
| 153 | switch(s->halign) { |
|---|
| 154 | case S_LeftAlign: |
|---|
| 155 | alignment = FloatToFract(0.); |
|---|
| 156 | break; |
|---|
| 157 | case S_CenterAlign: default: |
|---|
| 158 | alignment = kATSUCenterAlignment; |
|---|
| 159 | break; |
|---|
| 160 | case S_RightAlign: |
|---|
| 161 | alignment = FloatToFract(1.); |
|---|
| 162 | } |
|---|
| 163 | |
|---|
| 164 | s->usablewidth = resX - s->marginl - s->marginr; |
|---|
| 165 | width = IntToFixed(s->usablewidth); |
|---|
| 166 | ATSUCreateTextLayout(&s->layout); |
|---|
| 167 | ATSUSetLayoutControls(s->layout,sizeof(vals) / sizeof(ATSUAttributeValuePtr),tags,sizes,vals); |
|---|
| 168 | } |
|---|
| 169 | } |
|---|
| 170 | |
|---|
| 171 | #define fv(sn, fn) if (field = [style objectForKey:@"" # sn]) s.fn = [field doubleValue]; |
|---|
| 172 | #define iv(sn, fn) if (field = [style objectForKey:@"" # sn]) s.fn = [field intValue]; |
|---|
| 173 | #define sv(sn, fn) if (field = [style objectForKey:@"" # sn]) s.fn = [field retain]; |
|---|
| 174 | #define cv(sn, fn) if (field = [style objectForKey:@"" # sn]) s.color.fn = SSAParseColor(field); |
|---|
| 175 | #define bv(sn, fn) if (field = [style objectForKey:@"" # sn]) s.fn = [field intValue] != 0; |
|---|
| 176 | |
|---|
| 177 | -(void)setupStyles:(NSDictionary*)sDict |
|---|
| 178 | { |
|---|
| 179 | defaultstyle = NULL; |
|---|
| 180 | |
|---|
| 181 | if ([sDict count] > 0) { |
|---|
| 182 | NSEnumerator *sEnum = [sDict objectEnumerator]; |
|---|
| 183 | NSDictionary *style; int i=0; |
|---|
| 184 | |
|---|
| 185 | stylecount = [sDict count]; |
|---|
| 186 | styles = malloc(sizeof(ssastyleline[stylecount])); |
|---|
| 187 | while (style = [sEnum nextObject]) { |
|---|
| 188 | ssastyleline s = {0}; NSString *field; |
|---|
| 189 | |
|---|
| 190 | s.scalex = s.scaley = 100; |
|---|
| 191 | |
|---|
| 192 | sv(Name, name) |
|---|
| 193 | sv(Fontname, font) |
|---|
| 194 | fv(Fontsize, fsize) |
|---|
| 195 | cv(PrimaryColour, primary) |
|---|
| 196 | cv(SecondaryColour, secondary) |
|---|
| 197 | cv(OutlineColour, outline) |
|---|
| 198 | // cv(TertiaryColour, outline) |
|---|
| 199 | cv(BackColour, outline) |
|---|
| 200 | cv(BackColour, shadow) |
|---|
| 201 | bv(Bold, bold) |
|---|
| 202 | bv(Italic, italic) |
|---|
| 203 | bv(Underline, underline) |
|---|
| 204 | bv(StrikeOut, strikeout) |
|---|
| 205 | fv(ScaleX, scalex) |
|---|
| 206 | fv(ScaleY, scaley) |
|---|
| 207 | fv(Spacing, tracking) |
|---|
| 208 | fv(Angle, angle) |
|---|
| 209 | iv(BorderStyle, borderstyle) |
|---|
| 210 | fv(Outline, outline) |
|---|
| 211 | fv(Shadow, shadow) |
|---|
| 212 | iv(Alignment, alignment) |
|---|
| 213 | iv(MarginL, marginl) |
|---|
| 214 | iv(MarginR, marginr) |
|---|
| 215 | iv(MarginV, marginv) |
|---|
| 216 | |
|---|
| 217 | if ([s.font length] > 512) s.font = @"Helvetica"; |
|---|
| 218 | |
|---|
| 219 | if (version == S_SSA) s.alignment = SSA2ASSAlignment(s.alignment); |
|---|
| 220 | switch (s.alignment) {case 1: case 4: case 7: s.halign = S_LeftAlign; break; case 2: case 5: case 8: default: s.halign = S_CenterAlign; break; case 3: case 6: case 9: s.halign = S_RightAlign;} |
|---|
| 221 | switch (s.alignment) {case 1: case 2: case 3: default: s.valign = S_BottomAlign; break; case 4: case 5: case 6: s.valign = S_MiddleAlign; break; case 7: case 8: case 9: s.valign = S_TopAlign;} |
|---|
| 222 | |
|---|
| 223 | [self makeATSUStylesForSSAStyle:&s]; |
|---|
| 224 | |
|---|
| 225 | styles[i] = s; |
|---|
| 226 | if (!defaultstyle && [styles[i].name isEqualToString:@"Default"]) {defaultstyle = &styles[i]; disposedefaultstyle = NO;} |
|---|
| 227 | i++; |
|---|
| 228 | } |
|---|
| 229 | } |
|---|
| 230 | |
|---|
| 231 | if (!defaultstyle) { |
|---|
| 232 | disposedefaultstyle = YES; |
|---|
| 233 | defaultstyle = malloc(sizeof(ssastyleline)); |
|---|
| 234 | *defaultstyle = SSA_DefaultStyle; |
|---|
| 235 | [self makeATSUStylesForSSAStyle:defaultstyle]; |
|---|
| 236 | } |
|---|
| 237 | } |
|---|
| 238 | |
|---|
| 239 | -(SubLine *)movPacket:(int)i |
|---|
| 240 | { |
|---|
| 241 | return [_lines objectAtIndex:i]; |
|---|
| 242 | } |
|---|
| 243 | |
|---|
| 244 | static unsigned ParseSSATime(NSString *str) |
|---|
| 245 | { |
|---|
| 246 | int h,m,s,ms; |
|---|
| 247 | const char *cs = [str UTF8String]; |
|---|
| 248 | |
|---|
| 249 | sscanf(cs,"%d:%d:%d.%d",&h,&m,&s,&ms); |
|---|
| 250 | return ms + s * 100 + m * 100 * 60 + h * 100 * 60 * 60; |
|---|
| 251 | } |
|---|
| 252 | |
|---|
| 253 | static BOOL isinrange(unsigned base, unsigned test_s, unsigned test_e) |
|---|
| 254 | { |
|---|
| 255 | return (base >= test_s) && (base < test_e); |
|---|
| 256 | } |
|---|
| 257 | |
|---|
| 258 | static NSString *oneMKVPacket(NSDictionary *s) |
|---|
| 259 | { |
|---|
| 260 | return [NSString stringWithFormat:@"%d,%d,%@,%@,%0.4d,%0.4d,%0.4d,%@,%@\n", |
|---|
| 261 | [[s objectForKey:@"ReadOrder"] intValue], |
|---|
| 262 | [[s objectForKey:@"Layer"] intValue], |
|---|
| 263 | [s objectForKey:@"Style"], |
|---|
| 264 | [s objectForKey:@"Name"], |
|---|
| 265 | [[s objectForKey:@"MarginL"] intValue], |
|---|
| 266 | [[s objectForKey:@"MarginR"] intValue], |
|---|
| 267 | [[s objectForKey:@"MarginV"] intValue], |
|---|
| 268 | [s objectForKey:@"Effect"], |
|---|
| 269 | [s objectForKey:@"Text"]]; |
|---|
| 270 | } |
|---|
| 271 | |
|---|
| 272 | -(NSArray *)serializeSubLines:(NSMutableArray*)linesa |
|---|
| 273 | { |
|---|
| 274 | int num = [linesa count], i; |
|---|
| 275 | NSMutableArray *outa = [[NSMutableArray alloc] init]; |
|---|
| 276 | SubtitleSerializer *serializer = [[SubtitleSerializer alloc] init]; |
|---|
| 277 | SubLine *sl; |
|---|
| 278 | |
|---|
| 279 | for (i = 0; i < num; i++) { |
|---|
| 280 | NSDictionary *l = [linesa objectAtIndex:i];; |
|---|
| 281 | NSString *s,*e; |
|---|
| 282 | SubLine *li; |
|---|
| 283 | |
|---|
| 284 | s = [l objectForKey:@"Start"]; |
|---|
| 285 | e = [l objectForKey:@"End"]; |
|---|
| 286 | |
|---|
| 287 | li = [[[SubLine alloc] initWithLine:oneMKVPacket(l) start:ParseSSATime(s) end:ParseSSATime(e)] autorelease]; |
|---|
| 288 | |
|---|
| 289 | if (timescale != 1.) { |
|---|
| 290 | li->begin_time *= timescale; |
|---|
| 291 | li->end_time *= timescale; |
|---|
| 292 | } |
|---|
| 293 | |
|---|
| 294 | [serializer addLine:li]; |
|---|
| 295 | } |
|---|
| 296 | |
|---|
| 297 | [serializer setFinished:YES]; |
|---|
| 298 | |
|---|
| 299 | while (sl = [serializer getSerializedPacket]) { |
|---|
| 300 | [outa addObject:sl]; |
|---|
| 301 | } |
|---|
| 302 | |
|---|
| 303 | [serializer release]; |
|---|
| 304 | return outa; |
|---|
| 305 | } |
|---|
| 306 | |
|---|
| 307 | -(void) loadFile:(NSString*)path |
|---|
| 308 | { |
|---|
| 309 | NSString *ssa = [[NSString stringFromUnknownEncodingFile:path] stringByStandardizingNewlines]; |
|---|
| 310 | if (!ssa) return; |
|---|
| 311 | NSArray *lines = [ssa componentsSeparatedByString:@"\n"]; |
|---|
| 312 | NSEnumerator *lenum = [lines objectEnumerator]; |
|---|
| 313 | NSString *nextLine, *styleType, *ns; |
|---|
| 314 | NSArray *format; |
|---|
| 315 | NSMutableDictionary *headers, *styleDict; |
|---|
| 316 | NSMutableArray *doclines; |
|---|
| 317 | NSCharacterSet *ws = [NSCharacterSet whitespaceCharacterSet]; |
|---|
| 318 | unichar cai; |
|---|
| 319 | int formatc; |
|---|
| 320 | int readorder = 0; |
|---|
| 321 | |
|---|
| 322 | headers = [[NSMutableDictionary alloc] init]; |
|---|
| 323 | styleDict = [NSMutableDictionary dictionary]; |
|---|
| 324 | doclines = [NSMutableArray array]; |
|---|
| 325 | |
|---|
| 326 | if (![(NSString*)[lenum nextObject] isEqualToString:@"[Script Info]"]) return; |
|---|
| 327 | while (1) { |
|---|
| 328 | ns = (NSString*)[lenum nextObject]; |
|---|
| 329 | if (!ns || [ns length] == 0) continue; |
|---|
| 330 | else { |
|---|
| 331 | cai = [ns characterAtIndex:0]; |
|---|
| 332 | |
|---|
| 333 | if (cai == ';') continue; |
|---|
| 334 | else if (cai == '[') {nextLine = ns; break;} |
|---|
| 335 | NSArray *pair = [ns pairSeparatedByString:@": "]; |
|---|
| 336 | if ([pair count] == 2) [headers setObject:[(NSString*)[pair objectAtIndex:1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] forKey:[pair objectAtIndex:0]]; |
|---|
| 337 | } |
|---|
| 338 | |
|---|
| 339 | } |
|---|
| 340 | |
|---|
| 341 | [self setupHeaders:headers width:640 height:480]; |
|---|
| 342 | |
|---|
| 343 | while (![nextLine isEqualToString:@"[Events]"]) nextLine = [lenum nextObject]; |
|---|
| 344 | while ([nextLine length] == 0) nextLine = [lenum nextObject]; |
|---|
| 345 | nextLine = [lenum nextObject]; |
|---|
| 346 | |
|---|
| 347 | NSArray *pair = [nextLine pairSeparatedByString:@": "]; |
|---|
| 348 | NSString *formatstring = (version == S_ASS) ? |
|---|
| 349 | @"Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text" |
|---|
| 350 | : |
|---|
| 351 | @"Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"; |
|---|
| 352 | |
|---|
| 353 | if ([pair count] == 2 && [(NSString*)[pair objectAtIndex:0] isEqualToString:@"Format"]) {formatstring = (NSString*)[pair objectAtIndex:1]; ns = [lenum nextObject];} else ns = nextLine; |
|---|
| 354 | format = [[formatstring stringByTrimmingCharactersInSet:ws] componentsSeparatedByString:@", "]; |
|---|
| 355 | formatc = [format count]; |
|---|
| 356 | |
|---|
| 357 | while (1) { |
|---|
| 358 | if (!ns) break; |
|---|
| 359 | else if ([ns length] != 0) { |
|---|
| 360 | pair = [ns pairSeparatedByString:@": "]; |
|---|
| 361 | if ([pair count] == 2 && [(NSString*)[pair objectAtIndex:0] isEqualToString:@"Dialogue"]) { |
|---|
| 362 | NSArray *curl = [(NSString*)[pair objectAtIndex:1] componentsSeparatedByString:@"," count:formatc]; |
|---|
| 363 | int count = MIN([format count], [curl count]), i; |
|---|
| 364 | NSMutableDictionary *lDict = [NSMutableDictionary dictionary]; |
|---|
| 365 | for (i=0; i < count; i++) [lDict setObject:[curl objectAtIndex:i] forKey:[format objectAtIndex:i]]; |
|---|
| 366 | [lDict setObject:[NSString stringWithFormat:@"%d",readorder++] forKey:@"ReadOrder"]; |
|---|
| 367 | [doclines addObject:lDict]; |
|---|
| 368 | } |
|---|
| 369 | } |
|---|
| 370 | ns = [lenum nextObject]; |
|---|
| 371 | } |
|---|
| 372 | |
|---|
| 373 | _lines = [self serializeSubLines:doclines]; |
|---|
| 374 | |
|---|
| 375 | header = [[ssa substringToIndex:[ssa rangeOfString:@"[Events]" options:NSLiteralSearch].location] retain]; |
|---|
| 376 | } |
|---|
| 377 | |
|---|
| 378 | -(void) loadHeader:(NSString*)ssa width:(float)width height:(float)height |
|---|
| 379 | { |
|---|
| 380 | NSError *err; |
|---|
| 381 | NSStringEncoding se = NSUTF8StringEncoding; |
|---|
| 382 | if (!ssa) return; |
|---|
| 383 | NSArray *lines = [[ssa stringByStandardizingNewlines] componentsSeparatedByString:@"\n"]; |
|---|
| 384 | NSEnumerator *lenum = [lines objectEnumerator]; |
|---|
| 385 | NSString *nextLine, *styleType, *ns; |
|---|
| 386 | NSArray *format; |
|---|
| 387 | NSMutableDictionary *headers, *styleDict; |
|---|
| 388 | NSMutableArray *doclines; |
|---|
| 389 | NSCharacterSet *ws = [NSCharacterSet whitespaceCharacterSet]; |
|---|
| 390 | unichar cai; |
|---|
| 391 | int formatc; |
|---|
| 392 | int readorder = 0; |
|---|
| 393 | |
|---|
| 394 | headers = [[NSMutableDictionary alloc] init]; |
|---|
| 395 | styleDict = [NSMutableDictionary dictionary]; |
|---|
| 396 | doclines = [NSMutableArray array]; |
|---|
| 397 | |
|---|
| 398 | nextLine = [[lenum nextObject] stringByTrimmingCharactersInSet:ws]; |
|---|
| 399 | |
|---|
| 400 | if (![nextLine isEqualToString:@"[Script Info]"]) { |
|---|
| 401 | NSLog(@"\"%@\" is not a valid SSA header?",nextLine); |
|---|
| 402 | return; |
|---|
| 403 | } |
|---|
| 404 | while (1) { |
|---|
| 405 | ns = (NSString*)[lenum nextObject]; |
|---|
| 406 | if (!ns || [ns length] == 0) continue; |
|---|
| 407 | else { |
|---|
| 408 | cai = [ns characterAtIndex:0]; |
|---|
| 409 | |
|---|
| 410 | if (cai == ';') continue; |
|---|
| 411 | else if (cai == '[') {nextLine = ns; break;} |
|---|
| 412 | NSArray *pair = [ns pairSeparatedByString:@": "]; |
|---|
| 413 | if ([pair count] == 2) [headers setObject:[(NSString*)[pair objectAtIndex:1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] forKey:[pair objectAtIndex:0]]; |
|---|
| 414 | } |
|---|
| 415 | |
|---|
| 416 | } |
|---|
| 417 | |
|---|
| 418 | [self setupHeaders:headers width:width height:height]; |
|---|
| 419 | |
|---|
| 420 | while (!([nextLine isEqualToString:@"[V4 Styles]"] || [nextLine isEqualToString:@"[V4+ Styles]"])) nextLine = [lenum nextObject]; |
|---|
| 421 | styleType = nextLine; |
|---|
| 422 | while ([nextLine length] == 0) nextLine = [lenum nextObject]; |
|---|
| 423 | nextLine = [lenum nextObject]; |
|---|
| 424 | |
|---|
| 425 | NSArray *pair = [nextLine pairSeparatedByString:@": "]; |
|---|
| 426 | NSString *formatstring = ([styleType isEqualToString:@"[V4+ Styles]"]) ? |
|---|
| 427 | @"Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding" |
|---|
| 428 | : |
|---|
| 429 | @"Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding"; |
|---|
| 430 | |
|---|
| 431 | if ([pair count] == 2 && [(NSString*)[pair objectAtIndex:0] isEqualToString:@"Format"]) {formatstring = (NSString*)[pair objectAtIndex:1]; ns = [lenum nextObject];} else ns = nextLine; |
|---|
| 432 | |
|---|
| 433 | format = [[formatstring stringByTrimmingCharactersInSet:ws] componentsSeparatedByString:@", "]; |
|---|
| 434 | formatc = [format count]; |
|---|
| 435 | |
|---|
| 436 | while (1) { |
|---|
| 437 | if (!ns) break; |
|---|
| 438 | else if ([ns length] != 0) { |
|---|
| 439 | cai = [ns characterAtIndex:0]; |
|---|
| 440 | |
|---|
| 441 | if (cai == '[') {nextLine = ns; break;} |
|---|
| 442 | if (cai != ';' && cai != '!') { |
|---|
| 443 | NSArray *pair = [ns pairSeparatedByString:@": "]; |
|---|
| 444 | if ([pair count] == 2) { |
|---|
| 445 | NSArray *style = [(NSString*)[pair objectAtIndex:1] componentsSeparatedByString:@"," count:formatc]; // bug in SSA: font names with , break it |
|---|
| 446 | int count = MIN([format count], [style count]), i; |
|---|
| 447 | NSMutableDictionary *styled = [NSMutableDictionary dictionary]; |
|---|
| 448 | for (i=0; i < count; i++) [styled setObject:[style objectAtIndex:i] forKey:[format objectAtIndex:i]]; |
|---|
| 449 | [styleDict setObject:styled forKey:[styled objectForKey:@"Name"]]; |
|---|
| 450 | } |
|---|
| 451 | } |
|---|
| 452 | } |
|---|
| 453 | ns = [lenum nextObject]; |
|---|
| 454 | } |
|---|
| 455 | |
|---|
| 456 | [self setupStyles:styleDict]; |
|---|
| 457 | } |
|---|
| 458 | |
|---|
| 459 | -(void)loadDefaultsWithWidth:(float)width height:(float)height |
|---|
| 460 | { |
|---|
| 461 | stylecount = 0; |
|---|
| 462 | styles = malloc(0); |
|---|
| 463 | defaultstyle = malloc(sizeof(ssastyleline)); |
|---|
| 464 | *defaultstyle = SSA_DefaultStyle; |
|---|
| 465 | disposedefaultstyle = YES; |
|---|
| 466 | resX = (width / height) * 480.; resY = 480; |
|---|
| 467 | timescale = 1; |
|---|
| 468 | collisiontype = Normal; |
|---|
| 469 | version = S_ASS; |
|---|
| 470 | |
|---|
| 471 | [self makeATSUStylesForSSAStyle:defaultstyle]; |
|---|
| 472 | } |
|---|
| 473 | |
|---|
| 474 | -(NSString*)header |
|---|
| 475 | { |
|---|
| 476 | return header; |
|---|
| 477 | } |
|---|
| 478 | |
|---|
| 479 | -(unsigned)packetCount |
|---|
| 480 | { |
|---|
| 481 | return [_lines count]; |
|---|
| 482 | } |
|---|
| 483 | @end |
|---|
| 484 | |
|---|
| 485 | ComponentResult LoadSubStationAlphaSubtitles(const FSRef *theDirectory, CFStringRef filename, Movie theMovie, Track *firstSubTrack) |
|---|
| 486 | { |
|---|
| 487 | ComponentResult err = noErr; |
|---|
| 488 | Track theTrack = NULL; |
|---|
| 489 | Media theMedia = NULL; |
|---|
| 490 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
|---|
| 491 | SSADocument *ssa = [[SSADocument alloc] init]; |
|---|
| 492 | Handle sampleHndl = NULL, headerHndl=NULL, drefHndl; |
|---|
| 493 | Ptr initData = NewPtr(0); |
|---|
| 494 | const char *header; |
|---|
| 495 | int i, packetCount, sampleLen; |
|---|
| 496 | ImageDescriptionHandle textDesc; |
|---|
| 497 | Rect movieBox; |
|---|
| 498 | UInt32 emptyDataRefExtension[2]; |
|---|
| 499 | TimeScale movieTimeScale = GetMovieTimeScale(theMovie); |
|---|
| 500 | UInt8 *path = malloc(PATH_MAX); |
|---|
| 501 | |
|---|
| 502 | FSRefMakePath(theDirectory, path, PATH_MAX); |
|---|
| 503 | |
|---|
| 504 | [ssa loadFile:[[NSString stringWithUTF8String:(char*)path] stringByAppendingPathComponent:(NSString*)filename]]; |
|---|
| 505 | |
|---|
| 506 | free(path); |
|---|
| 507 | |
|---|
| 508 | packetCount = [ssa packetCount]; |
|---|
| 509 | |
|---|
| 510 | textDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription)); |
|---|
| 511 | |
|---|
| 512 | header = [[ssa header] UTF8String]; |
|---|
| 513 | sampleLen = strlen(header); |
|---|
| 514 | PtrToHand(header, &headerHndl, sampleLen); |
|---|
| 515 | |
|---|
| 516 | drefHndl = NewHandleClear(sizeof(Handle) + 1); |
|---|
| 517 | |
|---|
| 518 | /* Explanation of next three lines: |
|---|
| 519 | * The magic 'data' atom, added to an in-memory data handler, allows its data to be saved by value to a reference movie. |
|---|
| 520 | * It does this by saving the whole thing in the MOV header. |
|---|
| 521 | * This is imperfectly documented. |
|---|
| 522 | */ |
|---|
| 523 | emptyDataRefExtension[0] = EndianU32_NtoB(sizeof(UInt32)*2); |
|---|
| 524 | emptyDataRefExtension[1] = EndianU32_NtoB(kDataRefExtensionInitializationData); |
|---|
| 525 | |
|---|
| 526 | PtrAndHand(&emptyDataRefExtension[0], drefHndl, sizeof(emptyDataRefExtension)); |
|---|
| 527 | |
|---|
| 528 | GetMovieBox(theMovie,&movieBox); |
|---|
| 529 | theTrack = CreatePlaintextSubTrack(theMovie, textDesc, 100, drefHndl, HandleDataHandlerSubType, 'SSA ', headerHndl, movieBox); |
|---|
| 530 | if (theTrack == NULL) { |
|---|
| 531 | err = GetMoviesError(); |
|---|
| 532 | goto bail; |
|---|
| 533 | } |
|---|
| 534 | |
|---|
| 535 | theMedia = GetTrackMedia(theTrack); |
|---|
| 536 | if (theMedia == NULL) { |
|---|
| 537 | err = GetMoviesError(); |
|---|
| 538 | goto bail; |
|---|
| 539 | } |
|---|
| 540 | |
|---|
| 541 | BeginMediaEdits(theMedia); |
|---|
| 542 | |
|---|
| 543 | for (i = 0; i < packetCount; i++) { |
|---|
| 544 | SubLine *p = [ssa movPacket:i]; |
|---|
| 545 | TimeRecord movieStartTime = {SInt64ToWide(p->begin_time), 100, 0}; |
|---|
| 546 | TimeValue sampleTime; |
|---|
| 547 | const char *str = [p->line UTF8String]; |
|---|
| 548 | sampleLen = strlen(str); |
|---|
| 549 | |
|---|
| 550 | PtrToHand(str,&sampleHndl,sampleLen); |
|---|
| 551 | |
|---|
| 552 | err=AddMediaSample(theMedia,sampleHndl,0,sampleLen, p->end_time - p->begin_time,(SampleDescriptionHandle)textDesc, 1, 0, &sampleTime); |
|---|
| 553 | if (err != noErr) {NSLog(@"error adding %d-%d",p->begin_time, p->end_time); err = GetMoviesError(); goto loopend;} |
|---|
| 554 | |
|---|
| 555 | ConvertTimeScale(&movieStartTime, movieTimeScale); |
|---|
| 556 | |
|---|
| 557 | err = InsertMediaIntoTrack(theTrack, movieStartTime.value.lo, sampleTime, p->end_time - p->begin_time, fixed1); |
|---|
| 558 | if (err != noErr) {err = GetMoviesError(); goto bail;} |
|---|
| 559 | |
|---|
| 560 | loopend: |
|---|
| 561 | DisposeHandle(sampleHndl); |
|---|
| 562 | } |
|---|
| 563 | |
|---|
| 564 | sampleHndl = NULL; |
|---|
| 565 | |
|---|
| 566 | EndMediaEdits(theMedia); |
|---|
| 567 | |
|---|
| 568 | if (*firstSubTrack == NULL) |
|---|
| 569 | *firstSubTrack = theTrack; |
|---|
| 570 | else |
|---|
| 571 | SetTrackAlternate(*firstSubTrack, theTrack); |
|---|
| 572 | |
|---|
| 573 | SetMediaLanguage(theMedia, GetFilenameLanguage(filename)); |
|---|
| 574 | |
|---|
| 575 | bail: |
|---|
| 576 | |
|---|
| 577 | [ssa release]; |
|---|
| 578 | [pool release]; |
|---|
| 579 | |
|---|
| 580 | if (err) { |
|---|
| 581 | if (theMedia) |
|---|
| 582 | DisposeTrackMedia(theMedia); |
|---|
| 583 | |
|---|
| 584 | if (theTrack) |
|---|
| 585 | DisposeMovieTrack(theTrack); |
|---|
| 586 | } |
|---|
| 587 | |
|---|
| 588 | if (textDesc) |
|---|
| 589 | DisposeHandle((Handle) textDesc); |
|---|
| 590 | |
|---|
| 591 | if (headerHndl) DisposeHandle((Handle)headerHndl); |
|---|
| 592 | if (sampleHndl) DisposeHandle(sampleHndl); |
|---|
| 593 | DisposeHandle((Handle)drefHndl); |
|---|
| 594 | |
|---|
| 595 | return err; |
|---|
| 596 | } |
|---|